Welcome to MinervaLab’s documentation!

Applications

Mathematical analysis of the van der Waals isotherms

Code: #118-000

File: apps/van_der_waals/mathematical_analysis.ipynb

Run it online: Binder


The aim of this notebook is to show the mathematical function of van der Waals isotherms.

Interface

The main interface (main_block_118_000) is divided in two HBox: top_block_118_000 and bottom_block_118_000. top_block_118_000 contains of a bqplot Figure (fig_118_001) and bottom_block_118_000 contains 4 bqplot Figures: fig_118_003, fig_118_004, fig_118_005 and fig_118_006. The slider zoom_slider controls the zoom of fig_118_001.

[1]:
from IPython.display import Image
Image(filename='../../static/images/apps/118-000_1.png')
[1]:
_images/apps_van_der_waals_mathematical_analysis_3_0.png
[2]:
Image(filename='../../static/images/apps/118-000_2.png')
[2]:
_images/apps_van_der_waals_mathematical_analysis_4_0.png

CSS

A custom css file is used to improve the interface of this application. It can be found here.

[1]:
from IPython.display import HTML
display(HTML("<head><link rel='stylesheet' type='text/css' href='./../../static/custom.css'></head>"))
display(HTML("<style>.container { width:100% !important; }</style>"))

Packages

[2]:
from bqplot import *
import bqplot as bq
import bqplot.marks as bqm
import bqplot.scales as bqs
import bqplot.axes as bqa

import ipywidgets as widgets

import urllib.parse
import webbrowser

import sys

Physical functions

This are the functions that have a physical meaning:

  • calculate_critic
  • get_absolute_isotherms
  • bar_to_atm
[3]:
def calculate_critic(a, b):

    """
        This function calculates the critic point
        (p_c, v_c, T_c) from given a and b parameters of
        the Van der Waals equation of state for real gases.

        :math:`(P + a \\frac{n^2}{V^2})(V - nb) = nRT`

        :math:`p_c = \\frac{a}{27 b^2}`
        :math:`v_c = 3b`
        :math:`T_c = \\frac{8a}{27 b R}`

   Args:
       a: Term related with the attraction between particles in
       L^2 bar/mol^2.\n
       b: Term related with the volume that is occupied by one
       mole of the molecules in L/mol.\n

   Returns:
       p_c: Critical pressure in bar.\n
       v_c: Critical volume in L/mol.\n
       T_c: Critical tenperature in K.\n

    """

    if b == 0.0:
        return None

    k_B = 1.3806488e-23 #m^2 kg s^-2 K^-1
    N_A = 6.02214129e23
    R = 0.082 * 1.01325 #bar L mol^-1 K^-1

    p_c = a/27.0/(b**2)
    v_c = 3.0*b
    T_c = 8.0*a/27.0/b/R

    return p_c, v_c, T_c
[4]:
def get_absolute_isotherms(a, b, v_values, T_values):
    """This function calculates the theoretical p(v, T) plane
        (in absolute coordinates) according to van der Waals
        equation of state from a given range of volumes
        and tenperatures.

    Args:
        a: Term related with the attraction between particles in
           L^2 bar/mol^2.\n
        b: Term related with the volume that is occupied by one
        mole of the molecules in L/mol.\n
        v_values: An array containing the values of v
        for which the isotherms must be calculated.\n
        T_values: An array containing the values of T for which
        the isotherms must be calculated.\n


    Returns:
        isotherms: A list consisted of numpy arrays containing the
        pressures of each isotherm.
    """
    isotherms = []

    R = 0.082 * 1.01325 #bar L mol^-1 K^-1

    for T in T_values:

        isot = []

        for v in v_values:

            p = R*T/(v - b) - (a/v**2)
            isot = np.append(isot, p)

        isotherms.append(isot)

    return isotherms
[5]:
def bar_to_atm(p_values):
    """This function changes the pressures of an array
    form bars to atm.

    Args:
        p_values: List consisted of pressures in bars.\n

    Returns:
        p_values: List consisted of pressures in atm.\n
    """

    p_values = np.array(p_values) * 0.9869

    return p_values

Main interface

[ ]:
#In this program we are going to use water's parameters
a = 5.536 #L^2 bar mol^-2
b = 0.03049 #L mol^-1

colors = ['#0079c4','#f09205','#21c400', '#850082']

p_c, v_c, T_c = calculate_critic(a, b)

p_c = p_c * 0.9869 #unit change from bar to atm

v_values = np.linspace(-5.0, 5.0, 3000) #L/mol
T_values = [0.9*T_c, 1.0*T_c, 1.1*T_c]

p_values = get_absolute_isotherms(a, b, v_values, T_values)
p_values = bar_to_atm(p_values)

v_values_hd = np.linspace(-20, 20, 10000)
p_values_hd = get_absolute_isotherms(a, b, v_values_hd, T_values)

#####################
######TOP BLOCK######
#####################

top_block_118_000 = widgets.VBox(
    [],
    layout=widgets.Layout(align_items='center')
)

scale_x_118_001 = bqs.LinearScale(min = min(v_values), max = max(v_values))
scale_y_118_001 = bqs.LinearScale(min =-500.0, max = 500.0)

axis_x_118_001 = bqa.Axis(
    scale=scale_x_118_001,
    tick_format='.1f',
    tick_style={'font-size': '15px'},
    num_ticks=5,
    grid_lines = 'none',
    grid_color = '#8e8e8e',
    label='v (L/mol)',
    label_location='middle',
    label_style={'stroke': 'black', 'default-size': 35},
    label_offset='50px'
)

axis_y_118_001 = bqa.Axis(
    scale=scale_y_118_001,
    tick_format='.0f',
    tick_style={'font-size': '15px'},
    num_ticks=5,
    grid_lines = 'none',
    grid_color = '#8e8e8e',
    orientation='vertical',
    label='p (atm)',
    label_location='middle',
    label_style={'stroke': 'red', 'default_size': 35},
    label_offset='50px'
)

fig_118_001 = Figure(
    title='',
    marks=[],
    axes=[axis_x_118_001, axis_y_118_001],
    animation_duration=500,
    legend_location='top-right',
    background_style= {'fill': 'white',  'stroke': 'black'},
    min_aspect_ratio=1.0,
    fig_margin=dict(top=70, bottom=60, left=80, right=30),
    toolbar = True,
    layout = widgets.Layout(),
)

marks = [
    bqm.Lines(
        x = [v_values for elem in p_values],
        y = p_values,
        scales = {'x': scale_x_118_001, 'y': scale_y_118_001},
        opacities = [1.0],
        visible = True,
        colors = colors,
    )
]

fig_118_001.marks = marks

prepare_export_fig_118_001_button = widgets.Button(
    description='Export',
    disabled=False,
    button_style='',
    tooltip='',
    layout=widgets.Layout(
        width='initial',
        align_self='center'
    )
)

prepare_export_fig_118_001_button.on_click(prepare_export)


zoom_slider = widgets.IntSlider(
    value=0,
    min=0,
    max=30,
    step=1,
    description='Zoom:',
    disabled=False,
    continuous_update=True,
    orientation='vertical',
    readout=False,
    readout_format='d',
    layout = widgets.Layout(margin='35px 0 0 0', height='80%')
)

zoom_slider.observe(update_scales, 'value')


# Calculate the values of the scales
x_min = np.linspace(scale_x_118_001.min, 0.0, zoom_slider.max+1)
x_max = np.linspace(scale_x_118_001.max, 5.0*v_c, zoom_slider.max+1)

y_min = np.linspace(scale_y_118_001.min, 0.0, zoom_slider.max+1)
y_max = np.linspace(scale_y_118_001.max, 456.0, zoom_slider.max+1)


change_view_button = widgets.ToggleButton(
    value=False,
    description='Presentation mode (OFF)',
    disabled=False,
    button_style='',
    tooltip='',
    icon='desktop',
    layout=widgets.Layout(
        width='initial',
        align_self='center'
    )
)

change_view_button.observe(change_view, 'value')

top_block_118_000.children = [
    change_view_button,
    widgets.HBox([
        widgets.VBox([
            fig_118_001,
            prepare_export_fig_118_001_button,
        ]),
        zoom_slider
    ])
]


#####################
####BOTTOM BLOCK#####
#####################

bottom_block_118_000 = widgets.HBox(
    [],
    layout=widgets.Layout(
        height='300px',
        width='100%'
    )
)

scale_x_118_003 = bqs.LinearScale(
    min = scale_x_118_001.min,
    max = scale_x_118_001.max
)

scale_y_118_003 = bqs.LinearScale(
    min = scale_y_118_001.min,
    max = scale_y_118_001.max
)

axis_x_118_003 = bqa.Axis(
    scale=scale_x_118_003,
    tick_format='.0f',
    tick_style={'font-size': '15px'},
    num_ticks=5,
    grid_lines = 'none',
    grid_color = '#8e8e8e',
    label='v (L/mol)',
    label_location='middle',
    label_style={'stroke': 'black', 'default-size': 35},
    label_offset='50px'
)

axis_y_118_003 = bqa.Axis(
    scale=scale_y_118_003,
    tick_format='.0f',
    tick_style={'font-size': '15px'},
    num_ticks=5,
    grid_lines = 'none',
    grid_color = '#8e8e8e',
    orientation='vertical',
    label='p (atm)',
    label_location='middle',
    label_style={'stroke': 'red', 'default_size': 35},
    label_offset='50px'
)

marks = [
    bqm.Lines(
        x = [v_values for elem in p_values],
        y = p_values,
        scales = {'x': scale_x_118_003, 'y': scale_y_118_003},
        opacities = [1.0],
        visible = True,
        colors = colors
    )
]

fig_118_003 = Figure(
    title='',
    marks=marks,
    axes=[axis_x_118_003, axis_y_118_003],
    animation_duration=0,
    legend_location='top-right',
    background_style= {'fill': 'white',  'stroke': 'black'},
    min_aspect_ratio=1.0,
    fig_margin=dict(top=50, bottom=60, left=80, right=30),
    toolbar = False,
    layout = widgets.Layout(height='90%', width='95%')
)

scale_x_118_004 = bqs.LinearScale(min = -2.0, max = 2.0)
scale_y_118_004 = bqs.LinearScale(min = -2.2*p_c, max = 2.2*p_c)

axis_x_118_004 = bqa.Axis(
    scale=scale_x_118_004,
    tick_format='.0f',
    tick_style={'font-size': '15px'},
    num_ticks=5,
    grid_lines = 'none',
    grid_color = '#8e8e8e',
    label='v (L/mol)',
    label_location='middle',
    label_style={'stroke': 'black', 'default-size': 35},
    label_offset='50px'
)

axis_y_118_004 = bqa.Axis(
    scale=scale_y_118_004,
    tick_format='.0f',
    tick_style={'font-size': '15px'},
    tick_values = [-500, -250, 0, 250, 500],
    grid_lines = 'none',
    grid_color = '#8e8e8e',
    orientation='vertical',
    label='p (atm)',
    label_location='middle',
    label_style={'stroke': 'red', 'default_size': 35},
    label_offset='50px'
)

marks = [
    bqm.Lines(
        x = [v_values_hd for elem in p_values_hd],
        y = p_values_hd,
        scales = {'x': scale_x_118_004, 'y': scale_y_118_004},
        opacities = [1.0],
        visible = True,
        colors = colors,
    )
]

fig_118_004 = Figure(
    title='',
    marks=marks,
    axes=[axis_x_118_004, axis_y_118_004],
    animation_duration=0,
    legend_location='top-right',
    background_style= {'fill': 'white',  'stroke': 'black'},
    min_aspect_ratio=1.0,
    fig_margin=dict(top=50, bottom=60, left=80, right=30),
    toolbar = True,
    layout = widgets.Layout(height='90%', width='95%')
)



scale_x_118_005 = bqs.LinearScale(min = 0.0, max = 2.0)
scale_y_118_005 = bqs.LinearScale(min = 0.0, max = 2.2*p_c)

axis_x_118_005 = bqa.Axis(
    scale=scale_x_118_005,
    tick_format='.1f',
    tick_style={'font-size': '15px'},
    num_ticks=5,
    grid_lines = 'none',
    grid_color = '#8e8e8e',
    label='v (L/mol)',
    label_location='middle',
    label_style={'stroke': 'black', 'default-size': 35},
    label_offset='50px'
)

axis_y_118_005 = bqa.Axis(
    scale=scale_y_118_005,
    tick_format='.0f',
    tick_style={'font-size': '15px'},
    tick_values = [0, 150, 300, 450],
    grid_lines = 'none',
    grid_color = '#8e8e8e',
    orientation='vertical',
    label='p (atm)',
    label_location='middle',
    label_style={'stroke': 'red', 'default_size': 35},
    label_offset='50px'
)

marks = [
    bqm.Lines(
        x = [v_values_hd for elem in p_values_hd],
        y = p_values_hd,
        scales = {'x': scale_x_118_005, 'y': scale_y_118_005},
        opacities = [1.0],
        visible = True,
        colors = colors,
    )
]

fig_118_005 = Figure(
    title='',
    marks=marks,
    axes=[axis_x_118_005, axis_y_118_005],
    animation_duration=0,
    legend_location='top-right',
    background_style= {'fill': 'white',  'stroke': 'black'},
    min_aspect_ratio=1.0,
    fig_margin=dict(top=50, bottom=60, left=80, right=30),
    toolbar = True,
    layout = widgets.Layout(height='90%', width='95%')
)

scale_x_118_006 = bqs.LinearScale(min = 0.5*v_c, max = 5.0*v_c)
scale_y_118_006 = bqs.LinearScale(min = 0.0, max = 2.0*p_c)

axis_x_118_006 = bqa.Axis(
    scale=scale_x_118_006,
    tick_format='.2f',
    tick_style={'font-size': '15px'},
    num_ticks=5,
    grid_lines = 'none',
    grid_color = '#8e8e8e',
    label='v (L/mol)',
    label_location='middle',
    label_style={'stroke': 'black', 'default-size': 35},
    label_offset='50px'
)

axis_y_118_006 = bqa.Axis(
    scale=scale_y_118_006,
    tick_format='.0f',
    tick_style={'font-size': '15px'},
    tick_values = [0, 100, 200, 300, 400],
    grid_lines = 'none',
    grid_color = '#8e8e8e',
    orientation='vertical',
    label='p (atm)',
    label_location='middle',
    label_style={'stroke': 'red', 'default_size': 35},
    label_offset='50px'
)

marks = [
    bqm.Lines(
        x = [v_values_hd for elem in p_values_hd],
        y = p_values_hd,
        scales = {'x': scale_x_118_006, 'y': scale_y_118_006},
        opacities = [1.0],
        visible = True,
        colors = colors,
    )
]

fig_118_006 = Figure(
    title='',
    marks=marks,
    axes=[axis_x_118_006, axis_y_118_006],
    animation_duration=0,
    legend_location='top-right',
    background_style= {'fill': 'white',  'stroke': 'black'},
    min_aspect_ratio=1.0,
    fig_margin=dict(top=50, bottom=60, left=80, right=30),
    toolbar = True,
    layout = widgets.Layout(height='90%', width='95%')
)

prepare_export_fig_118_003_button = widgets.Button(
    description='Export',
    disabled=False,
    button_style='',
    tooltip='',
    layout=widgets.Layout(
        width='initial',
        align_self='center'
    )
)

prepare_export_fig_118_003_button.on_click(prepare_export)

prepare_export_fig_118_004_button = widgets.Button(
    description='Export',
    disabled=False,
    button_style='',
    tooltip='',
    layout=widgets.Layout(
        width='initial',
        align_self='center'
    )
)

prepare_export_fig_118_004_button.on_click(prepare_export)

prepare_export_fig_118_005_button = widgets.Button(
    description='Export',
    disabled=False,
    button_style='',
    tooltip='',
    layout=widgets.Layout(
        width='initial',
        align_self='center'
    )
)

prepare_export_fig_118_005_button.on_click(prepare_export)

prepare_export_fig_118_006_button = widgets.Button(
    description='Export',
    disabled=False,
    button_style='',
    tooltip='',
    layout=widgets.Layout(
        width='initial',
        align_self='center'
    )
)

prepare_export_fig_118_006_button.on_click(prepare_export)

bottom_block_118_000.children = [
    widgets.VBox([
        fig_118_003,
        prepare_export_fig_118_003_button,
        ],
        layout=widgets.Layout(
            width='25%',
        )
    ),
    widgets.VBox([
        fig_118_004,
        prepare_export_fig_118_004_button,
    ],
        layout=widgets.Layout(
            width='25%',
        )
    ),
    widgets.VBox([
        fig_118_005,
        prepare_export_fig_118_005_button,
    ],
        layout=widgets.Layout(
            width='25%',
        )
    ),
    widgets.VBox([
        fig_118_006,
        prepare_export_fig_118_006_button,
    ],
        layout=widgets.Layout(
            width='25%',
        )
    ),
]

#####################
####MAIN BLOCK#####
#####################

main_block_118_000 = widgets.VBox(
    [],
    layout=widgets.Layout(align_items='center')
)

main_block_118_000.children = [
    top_block_118_000,
    bottom_block_118_000
]

figures = [
    fig_118_001,
    fig_118_003,
    fig_118_004,
    fig_118_005,
    fig_118_006,
]

main_block_118_000

Mathematical analysis of the effect of a and b parameters

Code: #119-000

File: apps/van_der_waals/parameters_analysis.ipynb

Run it online: Binder


The aim of this notebook is to visualize the effect of a and b parameters on van der Waals’ isotherms.

Interface

The main interface (main_block_119_000) is divided in two HBox: top_block_119_000 and bottom_block_119_000. top_block_119_000 contains of 2 bqplot Figures: fig_119_001 and fig_119_002.

[1]:
from IPython.display import Image
Image(filename='../../static/images/apps/119-000_1.png')
[1]:
_images/apps_van_der_waals_parameters_analysis_4_0.png

The sliders a_slider and b_slider update the values of \(a\) and \(b\) which updates the isotherms of fig_119_001 and fig_119_002.

[2]:
Image(filename='../../static/images/apps/119-000_2.png')
[2]:
_images/apps_van_der_waals_parameters_analysis_6_0.png

CSS

A custom css file is used to improve the interface of this application. It can be found here.

[1]:
from IPython.display import HTML
display(HTML("<head><link rel='stylesheet' type='text/css' href='./../../static/custom.css'></head>"))
display(HTML("<style>.container { width:100% !important; }</style>"))

Packages

[2]:
from bqplot import *
import bqplot as bq
import bqplot.marks as bqm
import bqplot.scales as bqs
import bqplot.axes as bqa

import ipywidgets as widgets

import urllib.parse
import webbrowser

import sys

Physical functions

This are the functions that have a physical meaning:

  • get_absolute_isotherms
  • calculate_critic
  • bar_to_atm
[3]:
def get_absolute_isotherms(a, b, v_values, T_values):
    """This function calculates the theoretical p(v, T) plane
        (in absolute coordinates) according to van der Waals
        equation of state from a given range of volumes
        and tenperatures.

    Args:
        a: Term related with the attraction between particles in
           L^2 bar/mol^2.\n
        b: Term related with the volume that is occupied by one
        mole of the molecules in L/mol.\n
        v_values: An array containing the values of v
        for which the isotherms must be calculated.\n
        T_values: An array containing the values of T for which
        the isotherms must be calculated.\n


    Returns:
        isotherms: A list consisted of numpy arrays containing the
        pressures of each isotherm.
    """
    isotherms = []

    R = 0.082 * 1.01325 #bar L mol^-1 K^-1

    for T in T_values:

        isot = []

        for v in v_values:

            p = R*T/(v - b) - (a/v**2)
            isot = np.append(isot, p)

        isotherms.append(isot)

    return isotherms
[4]:
def calculate_critic(a, b):

    """
        This function calculates the critic point
        (p_c, v_c, T_c) from given a and b parameters of
        the Van der Waals equation of state for real gases.

        :math:`(P + a \\frac{n^2}{V^2})(V - nb) = nRT`

        :math:`p_c = \\frac{a}{27 b^2}`
        :math:`v_c = 3b`
        :math:`T_c = \\frac{8a}{27 b R}`

   Args:
       a: Term related with the attraction between particles in
       L^2 bar/mol^2.\n
       b: Term related with the volume that is occupied by one
       mole of the molecules in L/mol.\n

   Returns:
       p_c: Critical pressure in bar.\n
       v_c: Critical volume in L/mol.\n
       T_c: Critical tenperature in K.\n

    """

    if b == 0.0:
        return None

    k_B = 1.3806488e-23 #m^2 kg s^-2 K^-1
    N_A = 6.02214129e23
    R = 0.082 * 1.01325 #bar L mol^-1 K^-1

    p_c = a/27.0/(b**2)
    v_c = 3.0*b
    T_c = 8.0*a/27.0/b/R

    return p_c, v_c, T_c
[5]:
def bar_to_atm(p_values):
    """This function changes the pressures of an array
    form bars to atm.

    Args:
        p_values: List consisted of pressures in bars.\n

    Returns:
        p_values: List consisted of pressures in atm.\n
    """

    p_values = np.array(p_values) * 0.9869

    return p_values

Main interface

[ ]:
#In this program we are going to use water's parameters
a = 5.536 #L^2 bar mol^-2
b = 0.03049 #L mol^-1

colors = ['#0079c4','#f09205','#21c400', '#850082']

p_c, v_c, T_c = calculate_critic(a, b)

p_c = p_c * 0.9869 #unit change from bar to atm

v_values = np.linspace(-5, 5, 3000) #L/mol
T_values = [0.9*T_c, 1.0*T_c, 1.1*T_c]

p_values = get_absolute_isotherms(a, b, v_values, T_values)
p_values = bar_to_atm(p_values)

#####################
######TOP BLOCK######
#####################

top_block_119_000 = widgets.VBox(
    [],
    layout=widgets.Layout(
        align_items='center',
    )
)

scale_x_119_001 = bqs.LinearScale(min = -0.5, max = 0.5)
scale_y_119_001 = bqs.LinearScale(min = -300, max = 300)

axis_x_119_001 = bqa.Axis(
    scale=scale_x_119_001,
    tick_format='.1f',
    tick_style={'font-size': '15px'},
    num_ticks=5,
    grid_lines = 'none',
    grid_color = '#8e8e8e',
    label='v (L/mol)',
    label_location='middle',
    label_style={'stroke': 'black', 'default-size': 35},
    label_offset='50px'
)

axis_y_119_001 = bqa.Axis(
    scale=scale_y_119_001,
    tick_format='.0f',
    tick_style={'font-size': '15px'},
    num_ticks=4,
    grid_lines = 'none',
    grid_color = '#8e8e8e',
    orientation='vertical',
    label='p (atm)',
    label_location='middle',
    label_style={'stroke': 'red', 'default_size': 35},
    label_offset='50px'
)

fig_119_001 = Figure(
    title='',
    marks=[],
    axes=[axis_x_119_001, axis_y_119_001],
    animation_duration=0,
    legend_location='top-right',
    background_style= {'fill': 'white',  'stroke': 'black'},
    min_aspect_ratio=1.0,
    fig_margin=dict(top=10, bottom=60, left=80, right=30),
    toolbar = True,
    layout=widgets.Layout(
        height='350px',
    )
)

marks = [
    bqm.Lines(
        x = [v_values for elem in p_values],
        y = p_values,
        scales = {'x': scale_x_119_001, 'y': scale_y_119_001},
        opacities = [1.0],
        visible = True,
        colors = colors,
    )
]

fig_119_001.marks = marks


tb_119_001 = Toolbar(figure=fig_119_001, layout=widgets.Layout(align_self='center'))

fig_119_002 = Figure(
    title='',
    marks=[],
    axes=[axis_x_119_001, axis_y_119_001],
    animation_duration=0,
    legend_location='top-right',
    background_style= {'fill': 'white',  'stroke': 'black'},
    min_aspect_ratio=1.0,
    fig_margin=dict(top=10, bottom=60, left=80, right=30),
    toolbar = True,
    layout=widgets.Layout(
        height='350px',
    )
)

fig_119_002.marks = marks

change_view_button = widgets.ToggleButton(
    value=False,
    description='Presentation mode (OFF)',
    disabled=False,
    button_style='',
    tooltip='',
    icon='desktop',
    layout=widgets.Layout(
        width='initial',
        align_self='center'
    )
)

change_view_button.observe(change_view, 'value')

prepare_export_fig_119_001_button = widgets.Button(
    description='Export',
    disabled=False,
    button_style='',
    tooltip='',
    layout=widgets.Layout(
        align_self='center'
    )
)

prepare_export_fig_119_001_button.on_click(prepare_export)

prepare_export_fig_119_002_button = widgets.Button(
    description='Export',
    disabled=False,
    button_style='',
    tooltip='',
    layout=widgets.Layout(
        align_self='center'
    )
)

prepare_export_fig_119_002_button.on_click(prepare_export)

top_block_119_000.children = [
    change_view_button,
    tb_119_001,
    widgets.HBox([
        widgets.VBox([
            prepare_export_fig_119_001_button,
            fig_119_001
        ]),
        widgets.VBox([
            prepare_export_fig_119_002_button,
            fig_119_002
        ])
    ])
]

#####################
######BOTTOM BLOCK###
#####################

bottom_block_119_000 = widgets.VBox(
    [],
    layout=widgets.Layout(
        align_items='center',
        width='100%',
        margin='30px 0 0 0'
    )
)

a_slider = widgets.FloatSlider(
    min=0,
    max=34.0,
    step=0.001,
    value=a,
    description='a',
    disabled=False,
    continuous_update=False,
    orientation='horizontal',
    readout=True,
    layout=widgets.Layout(width='90%'),
)

a_slider.observe(update_isotherms, 'value')

b_slider = widgets.FloatSlider(
    min=0,
    max=0.1735,
    step=0.0001,
    value=b,
    description='b',
    disabled=False,
    continuous_update=False,
    orientation='horizontal',
    readout=True,
    layout=widgets.Layout(width='90%'),
)

b_slider.observe(update_isotherms, 'value')

bottom_block_119_000.children = [
    widgets.HBox([
        a_slider,
        widgets.HTMLMath(
            value=r"\( \frac{L^2 bar}{mol^2} \)",
            layout=widgets.Layout(height='60px')
        )],
        layout=widgets.Layout(
            width='50%',
            height='100%'
        )
    ),
    widgets.HBox([
        b_slider,
        widgets.HTMLMath(
            value=r"\( \frac{L}{mol} \)",
            layout=widgets.Layout(height='60px')
        )],
        layout=widgets.Layout(
            width='50%',
            height='100%'
        )
    )
]

#####################
####MAIN BLOCK#####
#####################

main_block_119_000 = widgets.VBox(
    [],
    layout=widgets.Layout(align_items='center')
)

main_block_119_000.children = [
    top_block_119_000,
    bottom_block_119_000
]

figures = [
    fig_119_001,
    fig_119_002
]

main_block_119_000

Effect of a and b in van der Waals isotherms

Code: #114-000

File: apps/van_der_waals/effect_of_a_and_b.ipynb

Run it online: Binder


The aim of this notebook is to visualize the effect of a and b parameters on van der Waals’ isotherms.

Interface

The main interface (main_block_114_000) is divided in two HBox: top_block_114_000 and bottom_block_114_000. top_block_114_000 contains of 5 bqplot Figures: fig_114_001, fig_114_002, fig_114_003, fig_114_004 and fig_114_005.

[1]:
from IPython.display import Image
Image(filename='../../static/images/apps/114-000_1.png')
[1]:
_images/apps_van_der_waals_effect_of_a_and_b_4_0.png

The sliders a_slider_114_003 and b_slider_114_004 update the values of \(a\) and \(b\) which updates the isotherms of fig_114_003, fig_114_004 and fig_114_005. The button reset_button resets the values of \(a\) and \(b\).

[2]:
Image(filename='../../static/images/114-000_2.png')
[2]:
_images/apps_van_der_waals_effect_of_a_and_b_6_0.png

CSS

A custom css file is used to improve the interface of this application. It can be found here.

[3]:
from IPython.display import HTML
display(HTML("<head><link rel='stylesheet' type='text/css' href='./../../static/custom.css'></head>"))
display(HTML("<style>.container { width:100% !important; }</style>"))
display(HTML("<style>.widget-label { display: contents !important; }</style>"))
display(HTML("<style>.slider-container { margin: 12px !important; }</style>"))

Packages

[4]:
from bqplot import *
import bqplot as bq
import bqplot.marks as bqm
import bqplot.scales as bqs
import bqplot.axes as bqa

import ipywidgets as widgets

import urllib.parse
import webbrowser

import sys

Physical functions

This are the functions that have a physical meaning:

  • get_absolute_isotherms
  • calculate_critic
  • bar_to_atm
[5]:
def get_absolute_isotherms(a, b, v_values, T_values):
    """This function calculates the theoretical p(v, T) plane
        (in absolute coordinates) according to van der Waals
        equation of state from a given range of volumes
        and tenperatures.

    Args:
        a: Term related with the attraction between particles in
           L^2 bar/mol^2.\n
        b: Term related with the volume that is occupied by one
        mole of the molecules in L/mol.\n
        v_values: An array containing the values of v
        for which the isotherms must be calculated.\n
        T_values: An array containing the values of T for which
        the isotherms must be calculated.\n


    Returns:
        isotherms: A list consisted of numpy arrays containing the
        pressures of each isotherm.
    """
    isotherms = []

    R = 0.082 * 1.01325 #bar L mol^-1 K^-1

    for T in T_values:

        isot = []

        for v in v_values:

            p = R*T/(v - b) - (a/v**2)
            isot = np.append(isot, p)

        isotherms.append(isot)

    return isotherms
[6]:
def calculate_critic(a, b):

    """
        This function calculates the critic point
        (p_c, v_c, T_c) from given a and b parameters of
        the Van der Waals equation of state for real gases.

        :math:`(P + a \\frac{n^2}{V^2})(V - nb) = nRT`

        :math:`p_c = \\frac{a}{27 b^2}`
        :math:`v_c = 3b`
        :math:`T_c = \\frac{8a}{27 b R}`

   Args:
       a: Term related with the attraction between particles in
       L^2 bar/mol^2.\n
       b: Term related with the volume that is occupied by one
       mole of the molecules in L/mol.\n

   Returns:
       p_c: Critical pressure in bar.\n
       v_c: Critical volume in L/mol.\n
       T_c: Critical tenperature in K.\n

    """

    if b == 0.0:
        return None

    k_B = 1.3806488e-23 #m^2 kg s^-2 K^-1
    N_A = 6.02214129e23
    R = 0.082 * 1.01325 #bar L mol^-1 K^-1

    p_c = a/27.0/(b**2)
    v_c = 3.0*b
    T_c = 8.0*a/27.0/b/R

    return p_c, v_c, T_c
[7]:
def bar_to_atm(p_values):
    """This function changes the pressures of an array
    form bars to atm.

    Args:
        p_values: List consisted of pressures in bars.\n

    Returns:
        p_values: List consisted of pressures in atm.\n
    """

    p_values = np.array(p_values) * 0.9869

    return p_values

Main interface

[ ]:
a_initial = 5.536 #L^2 bar/mol^2
b_initial = 0.03049 #L/mol

a, b = a_initial, b_initial

p_c, v_c, T_c = calculate_critic(a, b)

T_values = [0.95*T_c, T_c, 1.2*T_c]
v_values = np.linspace(1.2*b, 10*v_c, 500)
colors = ['#0079c4','#f09205','#21c400']

p_values = get_absolute_isotherms(a, b, v_values, T_values)
p_values = bar_to_atm(p_values)

#######################################
###############FIGURES#################
#######################################


fig_114_001 = bq.Figure(
    title='p vs v (Fixed T)',
    marks=[],
    axes=[],
    animation_duration=0,
    legend_location='top-right',
    background_style= {'fill': 'white',  'stroke': 'black'},
    fig_margin=dict(top=70, bottom=60, left=80, right=30),
    toolbar = True,
    layout = widgets.Layout(width='100%', height='500px')
)

fig_114_002 = bq.Figure(
    title='',
    marks=[],
    axes=[],
    animation_duration=0,
    legend_location='top-right',
    background_style= {'fill': 'white',  'stroke': 'black'},
    fig_margin=dict(top=30, bottom=60, left=25, right=10),
    toolbar = True,
    layout = widgets.Layout(width='90%', height='40%')
)

fig_114_003 = bq.Figure(
    title='',
    marks=[],
    axes=[],
    animation_duration=0,
    legend_location='top-right',
    background_style= {'fill': 'white',  'stroke': 'black'},
    fig_margin=dict(top=10, bottom=60, left=25, right=10),
    toolbar = True,
    layout = widgets.Layout(width='90%', height='40%')
)

fig_114_004 = bq.Figure(
    title='',
    marks=[],
    axes=[],
    animation_duration=0,
    legend_location='top-right',
    background_style= {'fill': 'white',  'stroke': 'black'},
    fig_margin=dict(top=10, bottom=60, left=25, right=10),
    toolbar = True,
    layout = widgets.Layout(width='90%', height='40%')
)

fig_114_005 = bq.Figure(
    title='p vs v (Fixed T)',
    marks=[],
    axes=[],
    animation_duration=0,
    legend_location='top-right',
    background_style= {'fill': 'white',  'stroke': 'black'},
    fig_margin=dict(top=70, bottom=60, left=80, right=30),
    toolbar = True,
    layout = widgets.Layout(width='100%', height='500px')
)

scale_x = bqs.LinearScale(min = 0.0, max = max(v_values))
scale_y = bqs.LinearScale(min = 0, max = 2.0*p_c)

axis_x = bqa.Axis(
    scale=scale_x,
    tick_format='.2f',
    tick_style={'font-size': '15px'},
    tick_values = np.linspace(0, max(v_values), 5),
    grid_lines = 'none',
    grid_color = '#8e8e8e',
    label='v (L/mol)',
    label_location='middle',
    label_style={'stroke': 'black', 'default-size': 35},
    label_offset='50px'
)

axis_y = bqa.Axis(
    scale=scale_y,
    tick_format='.1f',
    tick_style={'font-size': '15px'},
    tick_values = np.linspace(0, 2.0*p_c, 4),
    grid_lines = 'none',
    grid_color = '#8e8e8e',
    orientation='vertical',
    label='p (atm)',
    label_location='middle',
    label_style={'stroke': 'red', 'default_size': 35},
    label_offset='50px'
)

axis_x_no_ticks = bqa.Axis(
    scale=scale_x,
    tick_format='.2f',
    tick_style={'font-size': '15px'},
    num_ticks=0,
    grid_lines = 'none',
    grid_color = '#8e8e8e',
    label='v (L/mol)',
    label_location='middle',
    label_style={'stroke': 'black', 'default-size': 35},
    label_offset='15px'
)

axis_y_no_ticks = bqa.Axis(
    scale=scale_y,
    tick_format='.0f',
    tick_style={'font-size': '15px'},
    num_ticks=0,
    grid_lines = 'none',
    grid_color = '#8e8e8e',
    orientation='vertical',
    label='p (atm)',
    label_location='middle',
    label_style={'stroke': 'red', 'default_size': 35},
    label_offset='15px'
)

fig_114_001.axes = [axis_x, axis_y]
fig_114_002.axes = [axis_x_no_ticks, axis_y_no_ticks]
fig_114_003.axes = [axis_x_no_ticks, axis_y_no_ticks]
fig_114_004.axes = [axis_x_no_ticks, axis_y_no_ticks]
fig_114_005.axes = [axis_x, axis_y]

#######################################
###############MARKS###################
#######################################

x_values = [ v_values for i in range(len(p_values))]
y_values = []
color_values = []
label_values = []

for i in range(len(p_values)):

    y_values.append(p_values[i])
    color_values.append(colors[i])
    label_values.append(str(T_values[i]))

new_state = bqm.Lines(
    x = x_values,
    y = y_values,
    scales = {'x': scale_x, 'y': scale_y},
    opacities = [1.0 for elem in p_values],
    visible = True,
    colors = color_values,
    labels = label_values,
)

old_state = bqm.Lines(
    x = x_values,
    y = y_values,
    scales = {'x': scale_x, 'y': scale_y},
    opacities = [1.0 for elem in p_values],
    visible = True,
    colors = color_values,
    labels = label_values,
)

current_state = bqm.Lines(
    x = x_values[0],
    y = y_values[0],
    scales = {'x': scale_x, 'y': scale_y},
    opacities = [1.0 for elem in p_values],
    visible = True,
    colors = color_values,
    labels = label_values,
)

a_line = bqm.Lines(
    x = x_values[0],
    y = y_values[0],
    scales = {'x': scale_x, 'y': scale_y},
    opacities = [1.0 for elem in p_values],
    visible = True,
    colors = color_values,
    labels = label_values,
)

b_line = bqm.Lines(
    x = x_values[0],
    y = y_values[0],
    scales = {'x': scale_x, 'y': scale_y},
    opacities = [1.0 for elem in p_values],
    visible = True,
    colors = color_values,
    labels = label_values,
)

ideal_isotherms = get_absolute_isotherms(0, 0, v_values, T_values)
ideal_isotherms = bar_to_atm(ideal_isotherms)

ideal_line = bqm.Lines(
    x = x_values,
    y = ideal_isotherms,
    scales = {'x': scale_x, 'y': scale_y},
    opacities = [1.0 for elem in p_values],
    visible = True,
    colors = color_values,
    labels = label_values,
)

unique_isotherm = bqm.Lines(
    x = x_values[0],
    y = y_values[0],
    scales = {'x': scale_x, 'y': scale_y},
    opacities = [0.6],
    visible = True,
    colors = ['#c90000'],
    labels = [label_values[0]],
    stroke_width = 5
)

fig_114_001.marks = [old_state]
fig_114_002.marks = [current_state]
fig_114_003.marks = [a_line]
fig_114_004.marks = [b_line]
fig_114_005.marks = [ideal_line, unique_isotherm]

############################
##########WIDGETS###########
############################

a_slider_114_003 = widgets.FloatSlider(
    min=0.0,
    max=2.0*a,
    step=0.1,
    value=a,
    description='a',
    disabled=False,
    continuous_update=True,
    orientation='horizontal',
    readout=True,
    layout=widgets.Layout(width='90%')
)

a_slider_114_003.observe(update_isotherms, 'value')

b_slider_114_004 = widgets.FloatSlider(
    min=0.0,
    max=4.0*b,
    step=0.001,
    value=b,
    description='b',
    disabled=False,
    continuous_update=True,
    orientation='horizontal',
    readout=True,
    layout=widgets.Layout(width='90%')
)

b_slider_114_004.observe(update_isotherms, 'value')

reset_button = widgets.Button(
    description='Reset',
    disabled=False,
    button_style='',
    tooltip='Return to the original state',
)

reset_button.on_click(restart)

change_view_button = widgets.ToggleButton(
    value=False,
    description='Presentation mode (OFF)',
    disabled=False,
    button_style='',
    tooltip='',
    icon='desktop',
    layout=widgets.Layout(
        width='initial',
        align_self='center'
    )
)

change_view_button.observe(change_view, 'value')

prepare_export_fig_114_001_button = widgets.Button(
    description='Export',
    disabled=False,
    button_style='',
    tooltip='',
)

prepare_export_fig_114_001_button.on_click(prepare_export)

prepare_export_fig_114_005_button = widgets.Button(
    description='Export',
    disabled=False,
    button_style='',
    tooltip='',
)

prepare_export_fig_114_005_button.on_click(prepare_export)

############################
#########BLOCKS#############
############################

top_block_114_000 = widgets.HBox(
    [],
    layout=widgets.Layout(
        width='100%',
        align_self='center'
    )
)

top_block_114_000.children = [
    widgets.VBox([
        fig_114_001,
        prepare_export_fig_114_001_button
    ],
        layout=widgets.Layout(
            width='33%',
            align_items='center'
        )
    ),
    widgets.VBox([
        fig_114_002,
        fig_114_003,
        a_slider_114_003
    ],
        layout=widgets.Layout(
            width='16%',
            height='500px',
            align_items='center',
            margin='40px 0 0 0'
        )
    ),
    widgets.VBox([
        fig_114_002,
        fig_114_004,
        b_slider_114_004
    ],
        layout=widgets.Layout(
            width='16%',
            height='500px',
            align_items='center',
            margin='40px 0 0 0'
        )
    ),
    widgets.VBox([
        fig_114_005,
        prepare_export_fig_114_005_button
    ],
        layout=widgets.Layout(
            width='33%',
            align_items='center'
        )
    ),
]

bottom_block_114_000 = widgets.HBox(
    [],
    layout=widgets.Layout(
        width='100%',
        height='60px',
        align_self='center'
    )
)

bottom_block_114_000.children = [
    widgets.VBox([
        widgets.HTMLMath(
            value=r"\( (p + \frac{a}{v^2})(v - b) = k_B T \)"
        )
    ],
        layout=widgets.Layout(
            width='33%',
            align_items='center'
        )
    ),
    widgets.VBox(
        [reset_button],
        layout=widgets.Layout(
            width='33%',
            align_items='center'
        )
    ),
    widgets.VBox([
        widgets.HTMLMath(
            value=r"\( p v = k_B T \)"
        )
    ],
        layout=widgets.Layout(
            width='33%',
            align_items='center'
        )
    )
                         ]

main_block_114_000 = widgets.VBox(
    [],
    layout=widgets.Layout(
        width='100%',
        align_items='center'
    )
)

main_block_114_000.children = [
    change_view_button,
    top_block_114_000,
    bottom_block_114_000
]

figures = [
    fig_114_001,
    fig_114_002,
    fig_114_003,
    fig_114_004,
    fig_114_005,
]

main_block_114_000

Effect of a and b in van der Waals isotherms (reduced variables)

Code: #11C-000

File: apps/van_der_waals/effect_of_a_and_b_reduces.ipynb

Run it online: Binder


The aim of this notebook is to visualize the effect of a and b parameters on van der Waals’ isotherms (reduced variables).

Interface

The main interface (main_block_11C_000) is divided in two HBox: top_block_11C_000 and bottom_block_11C_000. top_block_11C_000 contains of 5 bqplot Figures: fig_11C_001, fig_11C_002, fig_11C_003, fig_11C_004 and fig_11C_005.

[1]:
from IPython.display import Image
Image(filename='../../static/images/apps/11C-000_1.png')
[1]:
_images/apps_van_der_waals_effect_of_a_and_b_reduced_3_0.png

CSS

A custom css file is used to improve the interface of this application. It can be found here.

[2]:
from IPython.display import HTML
display(HTML("<head><link rel='stylesheet' type='text/css' href='./../../static/custom.css'></head>"))
display(HTML("<style>.container { width:100% !important; }</style>"))
display(HTML("<style>.widget-label { display: contents !important; }</style>"))
display(HTML("<style>.slider-container { margin: 12px !important; }</style>"))

Packages

[3]:
from bqplot import *
import bqplot as bq
import bqplot.marks as bqm
import bqplot.scales as bqs
import bqplot.axes as bqa

import ipywidgets as widgets

import urllib.parse
import webbrowser

import sys

Physical functions

This are the functions that have a physical meaning:

  • get_relative_isotherms_params
[4]:
def get_relative_isotherms_params(alpha, beta, v_range, T_range):
    """This function calculates the theoretical p(v, T) plane
        (in reduced coordinates) according to van der Waals
        equation of state from a given range of volumes
        and tenperatures and taking into account the values
        of alpha and beta.

    Args:
        alpha: The value of the a parameter.\n
        alpha: The value of the b parameter.\n
        v_range: An array containing the values of v
        (in reduced coordinates)for which the isotherms must be
        calculated.\n
        T_range: An array containing the values of T
        (in reduced coordinates)for which the isotherms must be
        calculated.\n


    Returns:
        isotherms: A list consisted of numpy arrays containing the
        pressures of each isotherm.
    """

    isotherms = []

    for T in T_range:
        p_R = []
        for v in v_range:
            val = (8.0/3.0*T/(v - beta) - alpha/v**2)
            p_R = np.append(p_R, val)

        isotherms.append(p_R)

    return isotherms

Main interface

[ ]:
alpha_initial = 3.0 #0.0 < alpha < 3.0
beta_initial = 0.33 #0.0 < beta < 0.33

a, b = 5.536, 0.03049 #L^2 bar/mol^2, L/mol

T_values = [0.95, 1.0, 1.2]
v_values = np.linspace(0.4, 5.0, 500)
colors = ['#0079c4','#f09205','#21c400']

p_values = get_relative_isotherms_params(
    alpha_initial,
    beta_initial,
    v_values,
    T_values
)

#######################################
#######CREATE THE FIGURES##############
#######################################


fig_11C_001 = bq.Figure(
    title='p vs v (fixed T, reduced variables)',
    marks=[],
    axes=[],
    animation_duration=0,
    legend_location='top-right',
    background_style= {'fill': 'white',  'stroke': 'black'},
    fig_margin=dict(top=70, bottom=60, left=80, right=30),
    toolbar = True,
    layout = widgets.Layout(
        width='100%',
        height='500px'
    )
)

fig_11C_002 = bq.Figure(
    title='',
    marks=[],
    axes=[],
    animation_duration=0,
    legend_location='top-right',
    background_style= {'fill': 'white',  'stroke': 'black'},
    fig_margin=dict(top=30, bottom=60, left=25, right=10),
    toolbar = True,
    layout = widgets.Layout(
        width='90%',
        height='40%'
    )
)

fig_11C_003 = bq.Figure(
    title='',
    marks=[],
    axes=[],
    animation_duration=0,
    legend_location='top-right',
    background_style= {'fill': 'white',  'stroke': 'black'},
    fig_margin=dict(top=10, bottom=60, left=25, right=10),
    toolbar = True,
    layout = widgets.Layout(
        width='90%',
        height='40%'
    )
)

fig_11C_004 = bq.Figure(
    title='',
    marks=[],
    axes=[],
    animation_duration=0,
    legend_location='top-right',
    background_style= {'fill': 'white',  'stroke': 'black'},
    fig_margin=dict(top=10, bottom=60, left=25, right=10),
    toolbar = True,
    layout = widgets.Layout(
        width='90%',
        height='40%'
    )
)

fig_11C_005 = bq.Figure(
    title='p vs v (fixed T, reduced variables)',
    marks=[],
    axes=[],
    animation_duration=0,
    legend_location='top-right',
    background_style= {'fill': 'white',  'stroke': 'black'},
    fig_margin=dict(top=70, bottom=60, left=80, right=30),
    toolbar = True,
    layout = widgets.Layout(
        width='100%',
        height='500px'
    )
)

scale_x = bqs.LinearScale(min = 0.4, max = 5.0)
scale_y = bqs.LinearScale(min = 0, max = 2.0)

axis_x = bqa.Axis(
    scale=scale_x,
    tick_format='.2f',
    tick_style={'font-size': '15px'},
    tick_values = [0.5, 2.5, 5.0],
    grid_lines = 'none',
    grid_color = '#8e8e8e',
    label='v',
    label_location='middle',
    label_style={'stroke': 'black', 'default-size': 35},
    label_offset='50px'
)

axis_y = bqa.Axis(
    scale=scale_y,
    tick_format='.1f',
    tick_style={'font-size': '15px'},
    tick_values = [0, 1, 2],
    grid_lines = 'none',
    grid_color = '#8e8e8e',
    orientation='vertical',
    label='p',
    label_location='middle',
    label_style={'stroke': 'red', 'default_size': 35},
    label_offset='50px'
)

axis_x_no_ticks = bqa.Axis(
    scale=scale_x,
    tick_format='.2f',
    tick_style={'font-size': '15px'},
    num_ticks=0,
    grid_lines = 'none',
    grid_color = '#8e8e8e',
    label='v',
    label_location='middle',
    label_style={'stroke': 'black', 'default-size': 35},
    label_offset='15px'
)

axis_y_no_ticks = bqa.Axis(
    scale=scale_y,
    tick_format='.0f',
    tick_style={'font-size': '15px'},
    num_ticks=0,
    grid_lines = 'none',
    grid_color = '#8e8e8e',
    orientation='vertical',
    label='p',
    label_location='middle',
    label_style={'stroke': 'red', 'default_size': 35},
    label_offset='15px'
)

fig_11C_001.axes = [axis_x, axis_y]
fig_11C_002.axes = [axis_x_no_ticks, axis_y_no_ticks]
fig_11C_003.axes = [axis_x_no_ticks, axis_y_no_ticks]
fig_11C_004.axes = [axis_x_no_ticks, axis_y_no_ticks]
fig_11C_005.axes = [axis_x, axis_y]


#######################################
##############MARKS####################
#######################################

x_values = [v_values for i in range(len(p_values))]
y_values = []
color_values = []
label_values = []

for i in range(len(p_values)):

    y_values.append(p_values[i])
    color_values.append(colors[i])
    label_values.append(str(T_values[i]))

new_state = bqm.Lines(
    x = x_values,
    y = y_values,
    scales = {'x': scale_x, 'y': scale_y},
    opacities = [1.0 for elem in p_values],
    visible = True,
    colors = color_values,
    labels = label_values,
)

old_state = bqm.Lines(
    x = x_values,
    y = y_values,
    scales = {'x': scale_x, 'y': scale_y},
    opacities = [1.0 for elem in p_values],
    visible = True,
    colors = color_values,
    labels = label_values,
)

current_state = bqm.Lines(
    x = x_values[0],
    y = y_values[0],
    scales = {'x': scale_x, 'y': scale_y},
    opacities = [1.0 for elem in p_values],
    visible = True,
    colors = color_values,
    labels = label_values,
)

alpha_line = bqm.Lines(
    x = x_values[0],
    y = y_values[0],
    scales = {'x': scale_x, 'y': scale_y},
    opacities = [1.0 for elem in p_values],
    visible = True,
    colors = color_values,
    labels = label_values,
)

beta_line = bqm.Lines(
    x = x_values[0],
    y = y_values[0],
    scales = {'x': scale_x, 'y': scale_y},
    opacities = [1.0 for elem in p_values],
    visible = True,
    colors = color_values,
    labels = label_values,
)

ideal_isotherms = get_relative_isotherms_params(
    0,
    0,
    v_values,
    T_values
)

ideal_line = bqm.Lines(
    x = x_values,
    y = ideal_isotherms,
    scales = {'x': scale_x, 'y': scale_y},
    opacities = [1.0 for elem in p_values],
    visible = True,
    colors = color_values,
    labels = label_values,
)

unique_isotherm = bqm.Lines(
    x = x_values[0],
    y = y_values[0],
    scales = {'x': scale_x, 'y': scale_y},
    opacities = [0.6],
    visible = True,
    colors = ['#c90000'],
    labels = [label_values[0]],
    stroke_width = 5
)

fig_11C_001.marks = [old_state]
fig_11C_002.marks = [current_state]
fig_11C_003.marks = [alpha_line]
fig_11C_004.marks = [beta_line]
fig_11C_005.marks = [ideal_line, unique_isotherm]

alpha_slider_11C_003 = widgets.FloatSlider(
    min=0.0,
    max=5.0,
    step=0.01,
    value=alpha_initial,
    description=r"\( \alpha \)",
    disabled=False,
    continuous_update=True,
    orientation='horizontal',
    readout=True,
    layout=widgets.Layout(width='90%')
)

alpha_slider_11C_003.observe(update_isotherms, 'value')

beta_slider_11C_004 = widgets.FloatSlider(
    min=0.0,
    max=0.66,
    step=0.001,
    value=beta_initial,
    description=r"\( \beta \)",
    disabled=False,
    continuous_update=True,
    orientation='horizontal',
    readout=True,
    layout=widgets.Layout(width='90%')
)

beta_slider_11C_004.observe(update_isotherms, 'value')

return_button = widgets.Button(
    description='Reset',
    disabled=False,
    button_style='',
    tooltip='Return to the original state',
)

return_button.on_click(restart)

change_view_button = widgets.ToggleButton(
    value=False,
    description='Presentation mode (OFF)',
    disabled=False,
    button_style='',
    tooltip='',
    icon='desktop',
    layout=widgets.Layout(
        width='initial',
        align_self='center'
    )
)

change_view_button.observe(change_view, 'value')

prepare_export_fig_11C_001_button = widgets.Button(
    description='Export',
    disabled=False,
    button_style='',
    tooltip='',
)

prepare_export_fig_11C_001_button.on_click(prepare_export)

prepare_export_fig_11C_005_button = widgets.Button(
    description='Export',
    disabled=False,
    button_style='',
    tooltip='',
)

prepare_export_fig_11C_005_button.on_click(prepare_export)

top_block_11C_000 = widgets.HBox(
    [],
    layout=widgets.Layout(
        width='100%',
        align_self='center'
    )
)

top_block_11C_000.children = [
    widgets.VBox([
        fig_11C_001,
        prepare_export_fig_11C_001_button
    ],
        layout=widgets.Layout(
            width='33%',
            align_items='center'
        )
    ),
    widgets.VBox([
        fig_11C_002,
        fig_11C_003,
        alpha_slider_11C_003
    ],
        layout=widgets.Layout(
            width='16%',
            height='500px',
            align_items='center',
            margin='40px 0 0 0'
        )
    ),
    widgets.VBox([
        fig_11C_002,
        fig_11C_004,
        beta_slider_11C_004
    ],
        layout=widgets.Layout(
            width='16%',
            height='500px',
            align_items='center',
            margin='40px 0 0 0'
        )
    ),
    widgets.VBox([
        fig_11C_005,
        prepare_export_fig_11C_005_button
    ],
        layout=widgets.Layout(
            width='33%',
            align_items='center'
        )
    ),
]

bottom_block_11C_000 = widgets.HBox(
    [],
    layout=widgets.Layout(
        width='100%',
        height='60px',
        align_self='center'
    )
)

bottom_block_11C_000.children = [
    widgets.VBox([
        widgets.HTMLMath(
            value=r"\( (p_r + \frac{\alpha}{v_r^2})(v_r - \beta) = \frac{8}{3} T_r \)"
        )],
            layout=widgets.Layout(
                width='33%',
                align_items='center'
            )
    ),
    widgets.VBox(
        [return_button],
        layout=widgets.Layout(
            width='33%',
            align_items='center'
        )
    ),
    widgets.VBox([
        widgets.HTMLMath(
            value=r"\( p_r v_r = R T_r \)"
        )],
            layout=widgets.Layout(
                width='33%',
                align_items='center'
            )
    )
]

main_block_11C_000 = widgets.VBox(
    [],
    layout=widgets.Layout(
        width='100%',
        align_items='center'
    )
)

main_block_11C_000.children = [
    change_view_button,
    top_block_11C_000,
    bottom_block_11C_000
]

figures = [
    fig_11C_001,
    fig_11C_002,
    fig_11C_003,
    fig_11C_004,
    fig_11C_005,
]

main_block_11C_000

Critical points for various fluids

Code: #113-000

File: apps/van_der_waals/critical_points.ipynb

Run it online: Binder


The aim of this notebook is to visualize the critical points of various fluids.

Interface

The main interface (main_block_113_000) is divided in three VBox: top_block_113_000, middle_block_113_000 and bottom_block_113_000. top_block_113_000 contains a bqplot Figure (fig_113_001) and a qgrid chart (qgrid_table). middle_block_113_000 contains of 3 bqplot Figures: fig_113_002, fig_113_003 and fig_113_005.

[1]:
from IPython.display import Image
Image(filename='../../static/images/apps/113-000_1.png')
[1]:
_images/apps_van_der_waals_critical_points_3_0.png
[2]:
Image(filename='../../static/images/apps/113-000_2.png')
[2]:
_images/apps_van_der_waals_critical_points_4_0.png

CSS

A custom css file is used to improve the interface of this application. It can be found here.

[1]:
from IPython.display import HTML
display(HTML("<head><link rel='stylesheet' type='text/css' href='./../../static/custom.css'></head>"))
display(HTML("<style>.container { width:100% !important; }</style>"))

Packages

[2]:
from bqplot import *
import bqplot as bq
import bqplot.marks as bqm
import bqplot.scales as bqs
import bqplot.axes as bqa

import ipywidgets as widgets

import scipy

import qgrid
import pandas as pd

import urllib.parse
import webbrowser

import sys

Physical functions

This are the functions that have a physical meaning:

  • calculate_critic
  • get_absolute_isotherms
  • bar_to_atm
[3]:
def calculate_critic(a, b):

    """
        This function calculates the critic point
        (p_c, v_c, T_c) from given a and b parameters of
        the Van der Waals equation of state for real gases.

        :math:`(P + a \\frac{n^2}{V^2})(V - nb) = nRT`

        :math:`p_c = \\frac{a}{27 b^2}`
        :math:`v_c = 3b`
        :math:`T_c = \\frac{8a}{27 b R}`

   Args:
       a: Term related with the attraction between particles in
       L^2 bar/mol^2.\n
       b: Term related with the volume that is occupied by one
       mole of the molecules in L/mol.\n

   Returns:
       p_c: Critical pressure in bar.\n
       v_c: Critical volume in L/mol.\n
       T_c: Critical tenperature in K.\n

    """

    if b == 0.0:
        return None

    k_B = 1.3806488e-23 #m^2 kg s^-2 K^-1
    N_A = 6.02214129e23
    R = 0.082 * 1.01325 #bar L mol^-1 K^-1

    p_c = a/27.0/(b**2)
    v_c = 3.0*b
    T_c = 8.0*a/27.0/b/R

    return p_c, v_c, T_c
[4]:
def get_absolute_isotherms(a, b, v_values, T_values):
    """This function calculates the theoretical p(v, T) plane
        (in absolute coordinates) according to van der Waals
        equation of state from a given range of volumes
        and tenperatures.

    Args:
        a: Term related with the attraction between particles in
           L^2 bar/mol^2.\n
        b: Term related with the volume that is occupied by one
        mole of the molecules in L/mol.\n
        v_values: An array containing the values of v
        for which the isotherms must be calculated.\n
        T_values: An array containing the values of T for which
        the isotherms must be calculated.\n


    Returns:
        isotherms: A list consisted of numpy arrays containing the
        pressures of each isotherm.
    """
    isotherms = []

    R = 0.082 * 1.01325 #bar L mol^-1 K^-1

    for T in T_values:

        isot = []

        for v in v_values:

            p = R*T/(v - b) - (a/v**2)
            isot = np.append(isot, p)

        isotherms.append(isot)

    return isotherms
[5]:
def bar_to_atm(p_values):
    """This function changes the pressures of an array
    form bars to atm.

    Args:
        p_values: List consisted of pressures in bars.\n

    Returns:
        p_values: List consisted of pressures in atm.\n
    """

    p_values = np.array(p_values) * 0.9869

    return p_values
[6]:
def generate_critical_points(df):
    """This function takes a Pandas dataframe containing three
    columns (element, a, b) and returns four lists: pc, vc, Tc and names

    Args:
        df: Pandas dataframe consisted of three columns: element, a, b.\n

    Returns:
        pc: A numpy array consisted of the values of the critical pressures.\n
        vc: A numpy array consisted of the values of the critical volumes.\n
        Tc: A numpy array consisted of the values of the critical tenperatures.\n
        names: A list consisted of the names of the elements.\n
    """

    pc = []
    vc = []
    Tc = []

    names = []

    name_values = df.iloc[:,0]
    a_values, b_values, names_sorted = get_a_b_names(df)

    for i in range(len(a_values)):

        names.append(names_sorted[i])

        a = float(a_values[i])
        b = float(b_values[i])
        p, v, T = calculate_critic(a, b)

        pc = np.append(pc, p)
        vc = np.append(vc, v)
        Tc = np.append(Tc, T)

    return pc, vc, Tc, names
[7]:
def get_a_b_names(df):
    """This function takes a pandas dataframe containing the
    columns 'a (L2bar/mol2)', 'b (L/mol)' and 'Element' and return the
    lists of the colums sorted by the values of 'a (L2bar/mol2)'.

    Args:
        df: Pandas dataframe consisted of three columns: 'a (L2bar/mol2)',
        'b (L/mol)' and 'Element'.\n

    Returns:
        a_values_sorted: A list array consisted of the sorted values of 'a (L2bar/mol2)'.\n
        b_values_sorted: A list array consisted of the sorted values of 'b (L/mol)'.\n
        names_sorted: A list consisted of the names of the sorted values of 'Element'.\n
    """

    a_values = list(df['a (L2bar/mol2)'])
    b_values = list(df['b (L/mol)'])
    names = list(df['Element'])

    a_values_sorted = sorted(a_values)

    #Sort values of b depending on the order of a
    b_values_sorted = [y for _,y in sorted(zip(a_values, b_values))]
    names_sorted = [y for _,y in sorted(zip(a_values, names))]

    return a_values_sorted, b_values_sorted, names_sorted

Main interface

[17]:
#data taken from https://en.wikipedia.org/wiki/Van_der_Waals_constants_(data_page) using https://wikitable2csv.ggor.de/
#format: Element, a (L2bar/mol2), b (L/mol)

raw_data = [
"Acetic acid",17.71,0.1065,
"Acetic anhydride",20.158,0.1263,
"Acetone",16.02,0.1124,
"Acetonitrile",17.81,0.1168,
"Acetylene",4.516,0.0522,
"Ammonia",4.225,0.0371,
"Argon",1.355,0.03201,
"Benzene",18.24,0.1154,
"Bromobenzene",28.94,0.1539,
"Butane",14.66,0.1226,
"Carbon dioxide",3.640,0.04267,
"Carbon disulfide",11.77,0.07685,
"Carbon monoxide",1.505,0.03985,
"Carbon tetrachloride",19.7483,0.1281,
"Chlorine",6.579,0.05622,
"Chlorobenzene",25.77,0.1453,
"Chloroethane",11.05,0.08651,
"Chloromethane",7.570,0.06483,
"Cyanogen",7.769,0.06901,
"Cyclohexane",23.11,0.1424,
"Diethyl ether",17.61,0.1344,
"Diethyl sulfide",19.00,0.1214,
"Dimethyl ether",8.180,0.07246,
"Dimethyl sulfide",13.04,0.09213,
"Ethane",5.562,0.0638,
"Ethanethiol",11.39,0.08098,
"Ethanol",12.18,0.08407,
"Ethyl acetate",20.72,0.1412,
"Ethylamine",10.74,0.08409,
"Fluorobenzene",20.19,0.1286,
"Fluoromethane",4.692,0.05264,
"Freon",10.78,0.0998,
"Germanium tetrachloride",22.90,0.1485,
"Helium",0.0346,0.0238,
"Hexane",24.71,0.1735,
"Hydrogen",0.2476,0.02661,
"Hydrogen bromide",4.510,0.04431,
"Hydrogen chloride",3.716,0.04081,
"Hydrogen selenide",5.338,0.04637,
"Hydrogen sulfide",4.490,0.04287,
"Iodobenzene",33.52,0.1656,
"Krypton",2.349,0.03978,
"Mercury",8.200,0.01696,
"Methane",2.283,0.04278,
"Methanol",9.649,0.06702,
"Neon",0.2135,0.01709,
"Nitric oxide",1.358,0.02789,
"Nitrogen",1.370,0.0387,
"Nitrogen dioxide",5.354,0.04424,
"Nitrous oxide",3.832,0.04415,
"Oxygen",1.382,0.03186,
"Pentane",19.26,0.146,
"Phosphine",4.692,0.05156,
"Propane",8.779,0.08445,
"Radon",6.601,0.06239,
"Silane",4.377,0.05786,
"Silicon tetrafluoride",4.251,0.05571,
"Sulfur dioxide",6.803,0.05636,
"Tin tetrachloride",27.27,0.1642,
"Toluene",24.38,0.1463,
"Water",5.536,0.03049,
"Xenon",4.250,0.05105
]
[ ]:
"""

.. module:: critical_points.ipynb
    :sypnopsis: This module creates an interface to interact with the
    critical points of different fluids.\n

.. moduleauthor:: Jon Gabirondo López (jgabirondo001@ikasle.ehu.eus)

"""

# Prepare the database

data_array = np.array(raw_data)
data_reshaped = np.reshape(data_array, (-1,3)); #reshape in three columns

database = pd.DataFrame(
    data=data_reshaped,
    columns=["Element", "a (L2bar/mol2)", "b (L/mol)"]
)

# numpy converts all elements to 'object' and
# pandas interprets them as string, but I want a and b values to be float
database["a (L2bar/mol2)"] = np.round(
    pd.to_numeric(database["a (L2bar/mol2)"]),
    4
)

database["b (L/mol)"] = np.round(
    pd.to_numeric(database["b (L/mol)"]),
    5
)

a_values, b_values, names_sorted = get_a_b_names(database)

database["Element"] = names_sorted
database["a (L2bar/mol2)"] = a_values
database["b (L/mol)"] = b_values

# Show the database in QGrid chart.

grid_options = {
    # SlickGrid options
    'fullWidthRows': True,
    'syncColumnCellResize': True,
    'forceFitColumns': True,
    'defaultColumnWidth': 150,
    'rowHeight': 28,
    'enableColumnReorder': False,
    'enableTextSelectionOnCells': True,
    'editable': False, #lehen True
    'autoEdit': False,
    'explicitInitialization': True,

    # Qgrid options
    'maxVisibleRows': 7, #we have changed it to 5 (default = 15)
    'minVisibleRows': 7, #we have changed it to 5 (default = 8)
    'sortable': True,
    'filterable': True,
    'highlightSelectedCell': False,
    'highlightSelectedRow': True
}

qgrid_table = qgrid.show_grid(database, grid_options=grid_options)

qgrid_table.on(['filter_changed'], change_visible_points)
qgrid_table.on(['selection_changed'], change_selected_points)

# QGrid triggering actions
#[
#    'cell_edited',
#    'selection_changed',
#    'viewport_changed',
#    'row_added',
#    'row_removed',
#    'filter_dropdown_shown',
#    'filter_changed',
#    'sort_changed',
#    'text_filter_viewport_changed',
#    'json_updated'
#]

########################################
###########TOP BLOCK####################
########################################

top_block_113_000 = widgets.VBox(
    [],
    layout=widgets.Layout(
        width='100%',
        align_self='center',
    )
)

fig_113_004 = bq.Figure(
    title='a vs b',
    marks=[],
    axes=[],
    animation_duration=0,
    legend_location='top-right',
    background_style= {'fill': 'white',  'stroke': 'black'},
    fig_margin=dict(top=80, bottom=80, left=80, right=30),
    toolbar = True,
    layout = widgets.Layout(height='400px')
)

scale_x_004 = bqs.LinearScale(min = min(b_values), max = max(b_values))
scale_y_004 = bqs.LinearScale(min = min(a_values), max = max(a_values))

axis_x_004 = bqa.Axis(
    scale=scale_x_004,
    tick_format='.2f',
    tick_style={'font-size': '15px'},
    num_ticks=5,
    grid_lines = 'none',
    grid_color = '#8e8e8e',
    label='b (L/mol)',
    label_location='middle',
    label_style={'stroke': 'black', 'default-size': 35},
    label_offset='50px'
)

axis_y_004 = bqa.Axis(
    scale=scale_y_004,
    tick_format='.0f',
    tick_style={'font-size': '15px'},
    num_ticks=4,
    grid_lines = 'none',
    grid_color = '#8e8e8e',
    orientation='vertical',
    label='a (L^2 bar/mol)',
    label_location='middle',
    label_style={'stroke': 'red', 'default_size': 35},
    label_offset='50px'
)

fig_113_004.axes = [axis_x_004, axis_y_004]

scatter_113_004 = bqm.Scatter(
    name = '',
    x = b_values,
    y = a_values,
    scales = {'x': scale_x_004, 'y': scale_y_004},
    default_opacities = [0.2],
    visible = True,
    colors = ['#6a03a1'],
    names = names_sorted,
    display_names = False,
    labels=[],
    tooltip = tt
)

scatter_113_004.on_hover(hover_handler)

fig_113_004.marks = [scatter_113_004]

message1 = widgets.HTML(value='<p>You can choose the points directly in the table</p>')

change_view_button = widgets.ToggleButton(
    value=False,
    description='Presentation mode (OFF)',
    disabled=False,
    button_style='',
    tooltip='',
    icon='desktop',
    layout=widgets.Layout(
        width='initial',
        align_self='center'
    )
)

change_view_button.observe(change_view, 'value')

prepare_export_fig_113_004_button = widgets.Button(
    description='Export',
    disabled=False,
    button_style='',
    tooltip='',
)

prepare_export_fig_113_004_button.on_click(prepare_export)

top_block_113_000.children = [
    change_view_button,
    widgets.HBox([
        widgets.VBox([
            qgrid_table,
            message1
        ],
            layout=widgets.Layout(
                width='50%',
                margin='80px 0 0 0'
            )
        ),
        widgets.VBox([
            fig_113_004,
            prepare_export_fig_113_004_button
        ],
            layout=widgets.Layout(
                width='50%',
                align_items='center',
            )
        )
    ])
]


########################################
###########MIDDLE BLOCK#################
########################################

middle_block_113_000 = widgets.HBox(
    [],
    layout=widgets.Layout(
        width='100%',
        align_self='center',
        align_content='center'
    )
)

fig_113_002 = bq.Figure(
    title='pc vs vc',
    marks=[],
    axes=[],
    animation_duration=0,
    legend_location='top-right',
    background_style= {'fill': 'white',  'stroke': 'black'},
    fig_margin=dict(top=80, bottom=80, left=80, right=30),
    toolbar = True,
    layout = widgets.Layout(width='90%')
)

fig_113_003 = bq.Figure(
    title='Tc vs vc',
    marks=[],
    axes=[],
    animation_duration=0,
    legend_location='top-right',
    background_style= {'fill': 'white',  'stroke': 'black'},
    fig_margin=dict(top=80, bottom=80, left=80, right=30),
    toolbar = True,
    layout = widgets.Layout(width='90%')
)


pc, vc, Tc, names = generate_critical_points(database)

scale_x_v = bqs.LinearScale(min = min(vc), max = max(vc))
scale_y_p = bqs.LinearScale(min = min(pc), max = max(pc))
scale_y_T = bqs.LinearScale(min = min(Tc), max = max(Tc))


axis_x_v = bqa.Axis(
    scale=scale_x_v,
    tick_format='.2f',
    tick_style={'font-size': '15px'},
    num_ticks=5,
    grid_lines = 'none',
    grid_color = '#8e8e8e',
    label='v (L/mol)',
    label_location='middle',
    label_style={'stroke': 'black', 'default-size': 35},
    label_offset='50px'
)

axis_y_p = bqa.Axis(
    scale=scale_y_p,
    tick_format='.0f',#'0.2f',
    tick_style={'font-size': '15px'},
    num_ticks=4,
    grid_lines = 'none',
    grid_color = '#8e8e8e',
    orientation='vertical',
    label='p (atm)',
    label_location='middle',
    label_style={'stroke': 'red', 'default_size': 35},
    label_offset='50px'
)

axis_y_T = bqa.Axis(
    scale=scale_y_T,
    tick_format='.0f',#'0.2f',
    tick_style={'font-size': '15px'},
    num_ticks=4,
    grid_lines = 'none',
    grid_color = '#8e8e8e',
    orientation='vertical',
    label='T (K)',
    label_location='middle',
    label_style={'stroke': 'red', 'default_size': 35},
    label_offset='50px'
)



fig_113_002.axes = [axis_x_v, axis_y_p]
fig_113_003.axes = [axis_x_v, axis_y_T]

scatter_113_002 = bqm.Scatter(
    name = '',
    x = vc,
    y = pc,
    scales = {'x': scale_x_v, 'y': scale_y_p},
    default_opacities = [0.2],
    visible = True,
    colors = ['#2807a3'],
    names = names,
    display_names = False,
    labels=[],
    tooltip = tt
)

scatter_113_002.on_hover(hover_handler)

scatter_113_003 = bqm.Scatter(
    name = '',
    x = vc,
    y = Tc,
    scales = {'x': scale_x_v, 'y': scale_y_T},
    default_opacities = [0.2 for v in vc],
    visible = True,
    colors = ['#f5426c'],
    names = names,
    display_names = False,
    labels=[],
    tooltip = tt
)

scatter_113_003.on_hover(hover_handler)


tracer_113_002 = bqm.Scatter(
    name = 'tracer_113_002',
    x = [1.0],
    y = [1.0],
    scales = {'x': scale_x_v, 'y': scale_y_p},
    default_opacities = [1.0],
    visible = True,
    colors=['black'],
)

tracer_113_003 = bqm.Scatter(
    name = 'tracer_113_003',
    x = [1.0],
    y = [1.0],
    scales = {'x': scale_x_v, 'y': scale_y_T},
    default_opacities = [1.0],
    visible = True,
    colors=['black'],
)

selected_113_002 = bqm.Scatter(
    name = 'selected_113_002',
    x = [],
    y = [],
    scales = {'x': scale_x_v, 'y': scale_y_p},
    default_opacities = [1.0],
    visible = True,
    display_names = False,
    colors = scatter_113_002.colors,
    tooltip = tt
)

selected_113_002.on_hover(hover_handler)

selected_113_003 = bqm.Scatter(
    name = 'selected_113_003',
    x = [],
    y = [],
    scales = {'x': scale_x_v, 'y': scale_y_T},
    default_opacities = [1.0],
    visible = True,
    display_names = False,
    colors = scatter_113_003.colors,
    tooltip = tt
)

selected_113_002.on_hover(hover_handler)

fig_113_002.marks = [
    scatter_113_002,
    selected_113_002,
    tracer_113_002
]

fig_113_003.marks = [
    scatter_113_003,
    selected_113_003,
    tracer_113_003
]

colors = ['#0079c4','#f09205','#21c400']

fig_113_005 = bq.Figure(
    title='p vs v',
    marks=[],
    axes=[],
    animation_duration=500,
    legend_location='top-right',
    background_style= {'fill': 'white',  'stroke': 'black'},
    fig_margin=dict(top=80, bottom=80, left=80, right=20),
    toolbar = True,
    layout = widgets.Layout(width='90%')
)

p_values = []
v_values = []

for i in range(len(a_values)):

    a = a_values[i]
    b = b_values[i]


    p_c, v_c, T_c = calculate_critic(a, b)

    v = np.linspace(0.45*v_c, 5.0*v_c, 300)
    T_values = [0.95*T_c, T_c, 1.2*T_c]

    p = get_absolute_isotherms(a, b, v, T_values)

    p_values.append(bar_to_atm(p))
    v_values.append(v)

v_mean = np.mean(v_values)
v_min = np.min(v_values)

p_mean = np.mean(p_values)
p_min = np.min(p_values)

scale_x_005 = bqs.LinearScale(min = 0.0, max = 1.2*v_mean)
scale_y_005 = bqs.LinearScale(min = 0.0, max = 1.2*p_mean)

axis_x_005 = bqa.Axis(
    scale=scale_x_005,
    tick_format='.2f',
    tick_style={'font-size': '15px'},
    num_ticks=5,
    grid_lines = 'none',
    grid_color = '#8e8e8e',
    label='v (L/mol)',
    label_location='middle',
    label_style={'stroke': 'black', 'default-size': 35},
    label_offset='50px'
)


axis_y_005 = bqa.Axis(
    scale=scale_y_005,
    tick_format='.0f',
    tick_style={'font-size': '15px'},
    num_ticks=4,
    grid_lines = 'none',
    grid_color = '#8e8e8e',
    orientation='vertical',
    label='p (atm)',
    label_location='middle',
    label_style={'stroke': 'red', 'default_size': 35},
    label_offset='50px'
)

fig_113_005.axes = [axis_x_005, axis_y_005]

marks = []

for i in range(len(a_values)):

    marks.append(bqm.Lines(
        x = v_values[i],
        y = p_values[i],
        scales = {'x': scale_x_005, 'y': scale_y_005},
        opacities = [1.0 for elem in p_values],
        visible = a == a_values[i],
        colors = colors,
        labels = [names_sorted[i]]
        )
    )

fig_113_005.marks = marks

prepare_export_fig_113_002_button = widgets.Button(
        description='Export',
        disabled=False,
        button_style='',
        tooltip='',
    )

prepare_export_fig_113_002_button.on_click(prepare_export)

prepare_export_fig_113_003_button = widgets.Button(
        description='Export',
        disabled=False,
        button_style='',
        tooltip='',
    )

prepare_export_fig_113_003_button.on_click(prepare_export)

prepare_export_fig_113_005_button = widgets.Button(
        description='Export',
        disabled=False,
        button_style='',
        tooltip='',
    )

prepare_export_fig_113_005_button.on_click(prepare_export)

middle_block_113_000.children = [
    widgets.VBox([
        fig_113_002,
        prepare_export_fig_113_002_button
    ],
        layout=widgets.Layout(
            width='33%',
            align_items='center',
        )
    ),
    widgets.VBox([
        fig_113_003,
        prepare_export_fig_113_003_button
    ],
        layout=widgets.Layout(
            width='33%',
            align_items='center',
        )
    ),
    widgets.VBox([
        fig_113_005,
        prepare_export_fig_113_005_button
    ],
        layout=widgets.Layout(
            width='33%',
            align_items='center',
        )
    )
]

########################################
###########BOTTOM BLOCK#################
########################################
bottom_block_113_000 = widgets.VBox(
    [],
    layout=widgets.Layout(
        align_items='center',
        width='100%',
        margin='30px 0 0 0'
    )
)

a_slider = widgets.SelectionSlider(
    options=a_values,
    value=a_values[0],
    description='a',
    disabled=False,
    continuous_update=True,
    orientation='horizontal',
    readout=True,
    layout=widgets.Layout(width='90%'),
)

a_slider.observe(update_sliders, 'value')

b_slider = widgets.SelectionSlider(
    options=b_values,
    value=b_values[0],
    description='b',
    disabled=False,
    continuous_update=True,
    orientation='horizontal',
    readout=True,
    layout=widgets.Layout(width='90%'),
)

b_slider.observe(update_sliders, 'value')

bottom_block_113_000.children = [
    widgets.HBox([
        a_slider,
        widgets.HTMLMath(
            value=r"\( \frac{L^2 bar}{mol^2} \)",
            layout=widgets.Layout(height='60px')
        )],
        layout=widgets.Layout(
            width='50%',
            height='100%'
        )
    ),
    widgets.HBox([
        b_slider,
        widgets.HTMLMath(
            value=r"\( \frac{L}{mol} \)",
            layout=widgets.Layout(height='60px')
        )],
        layout=widgets.Layout(width='50%', height='100%')
    )
]

########################################
###########MAIN BLOCK###################
########################################

main_block_113_000 = widgets.VBox(
    [],
    layout=widgets.Layout(align_content='center')
)

main_block_113_000.children = [
    top_block_113_000,
    middle_block_113_000,
    bottom_block_113_000
]

figures = [
    fig_113_002,
    fig_113_003,
    fig_113_004,
    fig_113_005
]

main_block_113_000

Compare elements’ isotherms

Code: #115-000

File: apps/van_der_waals/compare_elements.ipynb

Run it online: Binder


The aim of this Notebook is to compare the isotherms of different elements.

Interface

The main interface (main_block_115_000) is divided in two VBox: left_block and right_block. left_block consists of four bqplot Figures and right_block contains fig_115_001.

[1]:
from IPython.display import Image
Image(filename='../../static/images/apps/115-000_1.png')
[1]:
_images/apps_van_der_waals_compare_elements_4_0.png

CSS

A custom css file is used to improve the interface of this application. It can be found here.

[2]:
from IPython.display import HTML
display(HTML("<head><link rel='stylesheet' type='text/css' href='./../../static/custom.css'></head>"))
display(HTML("<style>.container { width:100% !important; }</style>"))

Packages

[3]:
from bqplot import *
import bqplot as bq
import bqplot.marks as bqm
import bqplot.scales as bqs
import bqplot.axes as bqa

import ipywidgets as widgets

import urllib.parse
import webbrowser

import sys

Physical functions

This are the functions that have a physical meaning:

  • calculate_critic
  • get_absolute_isotherms
  • get_relative_isotherms
  • bar_to_atm
[4]:
def calculate_critic(a, b):

    """
        This function calculates the critic point
        (p_c, v_c, T_c) from given a and b parameters of
        the Van der Waals equation of state for real gases.

        :math:`(P + a \\frac{n^2}{V^2})(V - nb) = nRT`

        :math:`p_c = \\frac{a}{27 b^2}`
        :math:`v_c = 3b`
        :math:`T_c = \\frac{8a}{27 b R}`

   Args:
       a: Term related with the attraction between particles in
       L^2 bar/mol^2.\n
       b: Term related with the volume that is occupied by one
       mole of the molecules in L/mol.\n

   Returns:
       p_c: Critical pressure in bar.\n
       v_c: Critical volume in L/mol.\n
       T_c: Critical tenperature in K.\n

    """

    if b == 0.0:
        return None

    k_B = 1.3806488e-23 #m^2 kg s^-2 K^-1
    N_A = 6.02214129e23
    R = 0.082 * 1.01325 #bar L mol^-1 K^-1

    p_c = a/27.0/(b**2)
    v_c = 3.0*b
    T_c = 8.0*a/27.0/b/R

    return p_c, v_c, T_c
[5]:
def get_absolute_isotherms(a, b, v_values, T_values):
    """This function calculates the theoretical p(v, T) plane
        (in absolute coordinates) according to van der Waals
        equation of state from a given range of volumes
        and tenperatures.

    Args:
        a: Term related with the attraction between particles in
           L^2 bar/mol^2.\n
        b: Term related with the volume that is occupied by one
        mole of the molecules in L/mol.\n
        v_values: An array containing the values of v
        for which the isotherms must be calculated.\n
        T_values: An array containing the values of T for which
        the isotherms must be calculated.\n


    Returns:
        isotherms: A list consisted of numpy arrays containing the
        pressures of each isotherm.
    """
    isotherms = []

    R = 0.082 * 1.01325 #bar L mol^-1 K^-1

    for T in T_values:

        isot = []

        for v in v_values:

            p = R*T/(v - b) - (a/v**2)
            isot = np.append(isot, p)

        isotherms.append(isot)

    return isotherms
[6]:
def get_relative_isotherms(v_range, T_range):
    """This function calculates the theoretical p(v, T) plane
        (in reduced coordinates) according to van der Waals
        equation of state from a given range of volumes
        and tenperatures.

    Args:
        v_range: An array containing the values of v
        (in reduced coordinates)for which the isotherms must be
        calculated.\n
        T_range: An array containing the values of T
        (in reduced coordinates)for which the isotherms must be
        calculated.\n


    Returns:
        isotherms: A list consisted of numpy arrays containing the
        pressures of each isotherm.
    """

    isotherms = []

    for T in T_range:
        p_R = []
        for v in v_range:
            val = (8.0/3.0*T/(v - 1.0/3.0) - 3.0/v**2)
            p_R = np.append(p_R, val)

        isotherms.append(p_R)

    return isotherms
[7]:
def bar_to_atm(p_values):
    """This function changes the pressures of an array
    form bars to atm.

    Args:
        p_values: List consisted of pressures in bars.\n

    Returns:
        p_values: List consisted of pressures in atm.\n
    """

    p_values = np.array(p_values) * 0.9869

    return p_values

Main interface

[ ]:
#(a, b, element's name)
parameters = [(5.536, 0.03049, 'Water'),
             (4.225, 0.0371, 'Ammonia'),
             (1.358, 0.02789, 'Nitric oxide'),
             (4.25, 0.05105, 'Xenon')]

colors = ['#0079c4','#f09205','#21c400', '#850082']

#I want to show the same range in v so you can compare the isotherms of all the elements
#so, let's calculate the critic point of the first one and use as a reference for the rest

p_c1, v_c1, T_c1 = calculate_critic(parameters[0][0], parameters[0][1])
v_values = np.linspace(0.8*parameters[0][1], 10*v_c1, 500)

scale_x = bqs.LinearScale(min = min(v_values), max = max(v_values))
scale_y = bqs.LinearScale(min = 0.0, max = 2.0*p_c1)

axis_x = bqa.Axis(
    scale=scale_x,
    tick_format='.2f',
    tick_style={'font-size': '15px'},
    tick_values=[0, 0.45, 0.9],
    grid_lines = 'none',
    grid_color = '#8e8e8e',
    label='v (L/mol)',
    label_location='middle',
    label_style={'stroke': 'black', 'default-size': 35},
    label_offset='50px'
)

axis_y = bqa.Axis(
    scale=scale_y,
    tick_format='.0f',
    tick_style={'font-size': '15px'},
    tick_values=[0, 200, 400],
    grid_lines = 'none',
    grid_color = '#8e8e8e',
    orientation='vertical',
    label='p (atm)',
    label_location='middle',
    label_style={'stroke': 'red', 'default_size': 35},
    label_offset='50px'
)

main_block_115_000 = widgets.VBox(
    [],
    layout=widgets.Layout(width='100%')
)

left_block = widgets.VBox(
    [],
    layout=widgets.Layout(width='60%')
)

right_block = widgets.VBox(
    [],
    layout=widgets.Layout(width='40%')
)

h_block_1 = widgets.HBox([])
left_block.children = [h_block_1]

if len(parameters) > 3:

    h_block_2 = widgets.HBox([])
    left_block.children = [
        h_block_1,
        h_block_2
    ]

figures = []

for i in range(len(parameters)):

    elem = parameters[i]

    a = elem[0]
    b = elem[1]
    name = elem[2]

    p_c, v_c, T_c = calculate_critic(a, b)

    T_values = [0.8*T_c, 0.95*T_c, T_c, 1.2*T_c]
    T_values_str = [str(t) for t in T_values]
    v_values = np.linspace(b+0.01, 0.9, 500)

    isotherms = get_absolute_isotherms(a, b, v_values, T_values)
    isotherms = bar_to_atm(isotherms)

    block = widgets.VBox(
        [],
        layout=widgets.Layout(width='100%')
    )

    marks = []

    lines = bqm.Lines(
        x = [v_values for elem in isotherms],
        y = isotherms,
        scales = {'x': scale_x, 'y': scale_y},
        opacities = [1.0],
        visible = True, #True, #t == '1.00',
        colors = colors,
        labels = T_values_str,
    )

    critical_point = bqm.Scatter(
        name = '',
        x = [v_c],
        y = [p_c],
        scales = {'x': scale_x, 'y': scale_y},
        default_opacities = [1.0],
        visible = True,
        colors = ['#2807a3'],
    )

    marks = [
        lines,
        critical_point
    ]

    fig = Figure(
        title=name,
        marks=marks,
        axes=[axis_x, axis_y],
        animation_duration=0,
        legend_location='top-right',
        background_style= {'fill': 'white',  'stroke': 'black'},
        min_aspect_ratio=1.0,
        fig_margin=dict(top=80, bottom=60, left=80, right=30),
        toolbar = True,
        layout = widgets.Layout(width='90%', height='250px')
    )

    figures.append(fig)

    block.children = [
        fig,
        widgets.HBox([
            widgets.HTML(value='a: '+str(a)),
            widgets.HTML(value='b: '+str(b)),
        ],
            layout=widgets.Layout(
                    align_self='center',
                    justify_content='space-around',
                    width='100%'
                )
        )

    ]

    if i > 1:
        h_block_2.children = h_block_2.children + (block,)

    else:
        h_block_1.children = h_block_1.children + (block,)

v_values = np.linspace(0.45, 5.0, 500)
T_values = [0.8, 0.95, 1.0, 1.2]
T_values_str = [str(t) for t in T_values]
relative_isotherms = get_relative_isotherms(v_values, T_values)

scale_x = bqs.LinearScale(min = 0.45, max = 5.0)
scale_y = bqs.LinearScale(min = 0.0, max = 2.0)

axis_x = bqa.Axis(
    scale=scale_x,
    tick_format='0.2f',
    tick_style={'font-size': '15px'},
    tick_values=[1,2,3,4,5],
    grid_lines = 'none',
    grid_color = '#8e8e8e',
    label='v (L/mol)',
    label_location='middle',
    label_style={'stroke': 'black', 'default-size': 35},
    label_offset='50px'
)

axis_y = bqa.Axis(
    scale=scale_y,
    tick_format='0.2f',
    tick_style={'font-size': '15px'},
    tick_values=[0,1,2],
    grid_lines = 'none',
    grid_color = '#8e8e8e',
    orientation='vertical',
    label='p (atm)',
    label_location='middle',
    label_style={'stroke': 'red', 'default_size': 35},
    label_offset='50px'
)

fig_115_001 = Figure(
    title='van der Waals isotherms (reduced variables)',
    marks=[],
    axes=[axis_x, axis_y],
    animation_duration=0,
    legend_location='top-right',
    background_style= {'fill': 'white',  'stroke': 'black'},
    min_aspect_ratio=1.0,
    fig_margin=dict(top=80, bottom=60, left=80, right=30),
    toolbar = True,
    layout = widgets.Layout(width='90%')
)

lines = bqm.Lines(
    x = [v_values for elem in relative_isotherms],
    y = relative_isotherms,
    scales = {'x': scale_x, 'y': scale_y},
    opacities = [1.0],
    visible = True,
    colors = colors,
    labels = T_values_str,
)

critical_point = bqm.Scatter(
    name = '',
    x = [1.0],
    y = [1.0],
    scales = {'x': scale_x, 'y': scale_y},
    default_opacities = [1.0],
    visible = True,
    colors = ['#2807a3'],
)

fig_115_001.marks = [
    lines,
    critical_point
]

right_block.children = [fig_115_001]

change_view_button = widgets.ToggleButton(
    value=False,
    description='Presentation mode (OFF)',
    disabled=False,
    button_style='',
    tooltip='',
    icon='desktop',
    layout=widgets.Layout(
        width='initial',
        align_self='center'
    )
)

change_view_button.observe(change_view, 'value')

prepare_export_fig_0_button = widgets.Button(
    description='Export '+parameters[0][2],
    disabled=False,
    button_style='',
    tooltip='',
)

prepare_export_fig_0_button.on_click(prepare_export)

prepare_export_fig_1_button = widgets.Button(
    description='Export '+parameters[1][2],
    disabled=False,
    button_style='',
    tooltip='',
)

prepare_export_fig_1_button.on_click(prepare_export)

prepare_export_fig_2_button = widgets.Button(
    description='Export '+parameters[2][2],
    disabled=False,
    button_style='',
    tooltip='',
)

prepare_export_fig_2_button.on_click(prepare_export)

prepare_export_fig_3_button = widgets.Button(
    description='Export '+parameters[3][2],
    disabled=False,
    button_style='',
    tooltip='',
)

prepare_export_fig_3_button.on_click(prepare_export)

prepare_export_fig_115_001_button = widgets.Button(
    description='Export',
    disabled=False,
    button_style='',
    tooltip='',
    layout=widgets.Layout(
        align_self = 'center',
    )
)

prepare_export_fig_115_001_button.on_click(prepare_export)

tenperatures_text =  widgets.HTML(
    value="<div style='width:30px;text-align:left;display:inline-block;margin-left:30px;" \
        + "border: 5px solid #0079c4;opacity: 0.5'> </div>" \
        + "  T = 0.8 Tc" \
        + "<div style='width:30px;text-align:left;display:inline-block;margin-left:30px;" \
        + "border: 5px solid #f09205;opacity: 0.5'> </div>" \
        + "  T = 0.95 Tc" \
        + "<div style='width:30px;text-align:left;display:inline-block;margin-left:30px;" \
        + "border: 5px solid #21c400;opacity: 0.5'> </div>" \
        + "  T = Tc" \
        + "<div style='width:30px;text-align:left;display:inline-block;margin-left:30px;" \
        + "border: 5px solid #850082;opacity: 0.5'> </div>" \
        + "  T = 1.2 Tc" \
)

left_block.children = left_block.children + (
    widgets.HBox([
        prepare_export_fig_0_button,
        prepare_export_fig_1_button,
        prepare_export_fig_2_button,
        prepare_export_fig_3_button
    ],
    layout=widgets.Layout(
        align_self = 'center',
    )
    ),
)

right_block.children = right_block.children + (
    prepare_export_fig_115_001_button,
    tenperatures_text
)

main_block_115_000.children = [
    change_view_button,
    widgets.HTMLMath(
        '$a: L^2 bar / mol \quad b: L/mol$',
        layout=widgets.Layout(
            align_self = 'center',
        )
    ),
    widgets.HBox([
        left_block,
        right_block,
    ])
]

figures.append(fig_115_001)

main_block_115_000

Maxwell’s construction on van der Waals isotherms

Code: #111-000

File: apps/van_der_waals/p_v_2D.ipynb

Run it online: Binder


Interface

The main interface (main_block_111_000) is divided in two Box: top_block_111_000 and middle_block_111_000.

top_block_111_000 contains the widgets to control the tenperatute range shown and the toggles of the different areas and isother types.

middle_block_111_000 contains the main figure (fig_111_000) and the different controlers to implement Maxwell’s construction: T_slider changes the selected isotherm and p_slider controls the isobaric line.

Some buttons are only available if maxwell_construction_checkbox is activated.

v_slider controls the position of the mark tracer and it is only available if show_tracer_checkbox is activated.

[1]:
from IPython.display import Image
Image(filename='../../static/images/apps/111-000_1.png')
[1]:
_images/apps_van_der_waals_p_v_2D_2_0.png

CSS

A custom css file is used to improve the interface of this application. It can be found here.

[2]:
from IPython.display import HTML
display(HTML("<head><link rel='stylesheet' type='text/css' href='./../../static/custom.css'></head>"))
display(HTML("<style>.container { width:100% !important; } .jupyter-button {white-space: normal !important;}</style>"))

Packages

[3]:
from bqplot import *
import bqplot as bq
import bqplot.marks as bqm
import bqplot.scales as bqs
import bqplot.axes as bqa

import ipywidgets as widgets

from scipy import interpolate
from scipy.signal import argrelextrema

import urllib.parse
import webbrowser

import sys

Physical functions

This are the functions that have a physical meaning:

  • get_relative_isotherms
  • experimetal_isotherms
  • get_roots
  • p_indefinite_integral
  • p_definite_integral
  • find_real_fixed_p
  • find_real_fixed_T
[4]:
def get_relative_isotherms(v_range, T_range):
    """This function calculates the theoretical p(v, T) plane
        (in reduced coordinates) according to van der Waals
        equation of state from a given range of volumes
        and tenperatures.

    Args:
        v_range: An array containing the values of v
        (in reduced coordinates)for which the isotherms must be
        calculated.\n
        T_range: An array containing the values of T
        (in reduced coordinates)for which the isotherms must be
        calculated.\n


    Returns:
        isotherms: A list consisted of numpy arrays containing the
        pressures of each isotherm.
    """

    isotherms = []

    for T in T_range:
        p_R = []
        for v in v_range:
            val = (8.0/3.0*T/(v - 1.0/3.0) - 3.0/v**2)
            p_R = np.append(p_R, val)

        isotherms.append(p_R)

    return isotherms
[5]:
def experimental_isotherms(p_range, v_range, T_range, fixed_p, fixed_T):
    """This function calculates the experimental p(v, T) plane
        (in reduced coordinates) according to van der Waals
        equation of state for a given range of volumes
        and tenperatures or for a given range of volumes
        and pressures.

    Args:
        p_range: An array containing the values of p
        (in reduced coordinates)for which the isotherms must be
        calculated. Only used if fixed_p == True.\n
        v_range: An array containing the values of v
        (in reduced coordinates)for which the isotherms must be
        calculated.\n
        T_range: An array containing the values of v
        (in reduced coordinates)for which the isotherms must be
        calculated. Only used if fixed_T == True.\n
        fixed_p: Boolean variable which represents if the isotherms
        must be calculated for a given pressures.\n
        fixed_T: Boolean variable which represents if the isotherms
        must be calculated for a given pressures.\n


    Returns:
        expe_data: A list consisted of numpy arrays containing the
        pressures of each theoretical isotherm.\n
        theo_data: A list consisted of numpy arrays containing the
        pressures of each theoretical isotherm.\n
        v_limits: A list consisted of arrays of the volume limits of
        the phase-transition of each subcritical isotherm.\n
        p_limits: A list consisted of arrays of the pressure limits of
        the phase-transition of each subcritical isotherm.\n
        tenperatures: A list consisted of the tenperatures of the
        isotherms.\n
    """

    if fixed_T:

        theo_data = get_relative_isotherms(v_range, T_range)
        expe_data = []

        v_limits = []
        p_limits = []

        p_range = np.linspace(0.001, 1.0, num=10000)
        pressures, v_isobaric_limits = find_real_fixed_T(p_range, T_range)

        for i in range(len(theo_data)):

            p_expe = []

            if i < len(v_isobaric_limits):

                    v_lim = v_isobaric_limits[i]

                    if len(v_lim) > 1: #check if there is only one point
                        for j in range(len(v_range)):

                            if v_range[j] > v_lim[0] and v_range[j] < v_lim[1]:
                                p_expe.append(pressures[i])

                            else:
                                p_expe.append(theo_data[i][j])

                        v_limits = np.append(v_limits, [v_lim[0], v_lim[1]])
                        p_limits = np.append(p_limits, [pressures[i], pressures[i]])

                    else:
                        p_expe = theo_data[i]
                        v_limits = np.append(v_limits, [1.0])
                        p_limits = np.append(p_limits, [1.0])

            else:

                p_expe = theo_data[i]

            expe_data.append(p_expe)

        tenperatures = T_range

        return expe_data, theo_data, p_limits, v_limits, tenperatures

    elif fixed_p:

        tenperatures, v_isobaric_limits = find_real_fixed_p(p_range, T_range)

        theo_data = get_relative_isotherms(v_range, tenperatures)
        expe_data = []

        v_limits = []
        p_limits = []

        for i in range(len(theo_data)):

            p_expe = []

            if i < len(v_isobaric_limits):

                    v_lim = v_isobaric_limits[i]

                    if len(v_lim) > 1: #check if there is only one point

                        for j in range(len(v_range)):

                            if v_range[j] > v_lim[0] and v_range[j] < v_lim[1]:
                                p_expe.append(p_range[i])

                            else:
                                p_expe.append(theo_data[i][j])

                        v_limits = np.append(
                            v_limits,
                            [v_lim[0],
                             v_lim[1]]
                        )
                        p_limits = np.append(
                            p_limits,
                            [p_range[i],
                             p_range[i]]
                        )

                    else:
                        p_expe = theo_data[i]
                        v_limits = np.append(v_limits, [1.0])
                        p_limits = np.append(p_limits, [1.0])

            else:

                p_expe = theo_data[i]

            expe_data.append(p_expe)

        return expe_data, theo_data, p_limits, v_limits, tenperatures
[6]:
def get_roots(p, T):
    """This function calculates the roots of a van der Waals
    isotherm of a given T and set of pressures.

    Args:
        p: Numpy array consisted of the pressures of the isotherm.\n
        T: Value of the tenperature.\n

    Returns:
        roots_in_range: A list consisted of the real roots.\n
    """


    roots = np.roots([1.0, - 1.0/3.0*(1.0 + 8.0*T/p), 3.0/p, -1.0/p])
    roots_in_range = []

    for root in roots:
        if np.isreal(root):
            root = np.real(root)
            if root > 0:
                roots_in_range.append(root)
    roots_in_range.sort()

    return roots_in_range
[7]:
def p_indefinite_integral(p_0, v_0, T):
    """This function calculates the indefinite integral between
    a van der Waals isotherm and a isobaric line.

    Args:
        p0: Isobaric line's pressure.\n
        v0: Value of the volume.\n
        T: Value of the tenperature.\n

    Returns:
        integral: Value of the indefinite integral between a
        van der Waals isotherm at T and a isobaric line of p0 at a
        volume v0.\n
    """

    integral = 8.0/3.0 * T *np.log(v_0 - 1.0/3.0) + 3.0/v_0 - p_0*v_0

    return integral
[8]:
def definite_integral(p_0, v_range, T):
    """This function 'p_indefinite_integral' function to calculate
    the definite integral between a van der Waals isotherm and a
    isobaric line.

    Args:
        p0: Isobaric line's pressure.\n
        v_range: Tuple or list consisted of volume limits.\n
        T: Value of the tenperature.\n

    Returns:
        integral: Value of the definite integral between a
        van der Waals isotherm at T and a isobaric line of p0 in a
        volume range v_range.\n
    """

    v_0, v_1 = v_range[0], v_range[1]

    integral = p_indefinite_integral(p_0, v_1, T) - p_indefinite_integral(p_0, v_0, T)

    return integral
[9]:
def find_real_fixed_T(p_values, T_values):
    """This function uses Maxwell's construction to find the
       pressures in which phase transition happens given some
       fixed tenperatures.\n

    Args:
        p_values: List of pressures in which the real isotherm is
        searched.\n
        T_values: List of tenperatures of the isotherms.\n


    Returns:
        pressures: List of pressures in which phase transition
        happens.\n
        v_range: Volume limits of phase transition zones.
    """

    eps = 1e-3

    pressures = []
    v_ranges = []

    for T in T_values:

        if T < 1.0:

            for p in p_values:

                roots = get_roots(p, T)

                if len(roots) == 3:

                    v_range = [roots[0], roots[2]]
                    area = definite_integral(p, v_range, T)

                    if abs(area) < eps:

                        pressures.append(p)
                        v_ranges.append(v_range)

                        break

        elif T == 1.0:

            pressures.append(1.0)
            v_ranges.append([1.0])

    return pressures, v_ranges
[10]:
def find_real_fixed_p(p_values, T_values):
    """This function uses Maxwell's construction to find the
       tenperatures in which phase transition happens given some
       fixed pressures.\n

    Args:
        p_values: List of pressures of the isotherms.\n
        T_values: List of tenperatures in which the real isotherm is
        searched.\n


    Returns:
        tenperatures: List of tenperatures in which phase transition
        happens.\n
        v_range: Volume limits of phase transition zones.
    """

    eps = 1e-3

    tenperatures = []
    v_ranges = []

    for p in p_values:

        if p < 1.0:

            for T in T_values:

                roots = get_roots(p, T)

                if len(roots) == 3:

                    v_range = [roots[0], roots[2]]
                    area = definite_integral(p, v_range, T)

                    if abs(area) < eps:

                        tenperatures.append(T)
                        v_ranges.append(v_range)

                        break

        elif p == 1.0:

            tenperatures.append(1.0)
            v_ranges.append([1.0])

    return tenperatures, v_ranges

Main interface

[ ]:
"""

.. module:: p_v_2D.ipynb
    :sypnopsis: This module creates an interface to interact with the
    van der Waals isotherms in the p(v, T) plane.\n

.. moduleauthor:: Jon Gabirondo López (jgabirondo001@ikasle.ehu.eus)

"""

saved = []

#############################
######TOP BLOCK##############
#############################

top_block_111_000 = widgets.VBox(
    [],
    layout=widgets.Layout(align_items='center')
)

change_view_button = widgets.ToggleButton(
    value=False,
    description='Presentation mode (OFF)',
    disabled=False,
    button_style='',
    tooltip='',
    icon='desktop',
    layout=widgets.Layout(
        width='auto'
    )
)

change_view_button.observe(change_view, 'value')

t_min_input = widgets.BoundedFloatText(
    value=0.85,
    min=0.5,
    max=3.0,
    step=0.005,
    description=r'\( T_{min}: \)',
    disabled=False
)

t_max_input = widgets.BoundedFloatText(
    value=1.05,
    min=0.5,
    max=3.0,
    step=0.005,
    description=r'\( T_{max}: \)',
    disabled=False
)

num_input = widgets.IntSlider(
    value=6,
    min=1,
    max=20,
    description=r'\( \# \)',
    disabled=False,
    continuous_update=True,
    orientation='horizontal',
    readout=True,
)

generate_button = widgets.Button(
    description='Generate',
    disabled=False,
    button_style='',
    tooltip='Generate isotherms in the selected T range.',
)

generate_button.on_click(change_isotherms)

#############################
#isotherm_type_block
#############################

theoretical_toggle = widgets.ToggleButton(
    value=True,
    description='Theoretical',
    disabled=False,
    button_style='',
    tooltip='Show theoretical isotherms',
    style = {'description_width': 'initial'},
    layout = widgets.Layout(
        align_self='center',
        width='100%'
    )
)

experimental_toggle = widgets.ToggleButton(
    value=False,
    description='Experimental',
    disabled=False,
    button_style='',
    tooltip='Show experimental isotherms',
    style = {'description_width': 'initial'},
    layout = widgets.Layout(
        align_self='center',
        width='100%'
    ),
)

theoretical_toggle.observe(change_isotherm_type, 'value')
experimental_toggle.observe(change_isotherm_type, 'value')

show_transition_limits_checkbox = widgets.Checkbox(
    value=False,
    description='Show transition limits',
    disabled=False,
    layout = widgets.Layout(width='148px !important')
)

show_transition_line_checkbox = widgets.Checkbox(
    value=False,
    description='Show transition line',
    disabled=False,
    layout = widgets.Layout(width='148px !important')
)

show_existence_limits_checkbox = widgets.Checkbox(
    value=False,
    description='Show existence limits',
    disabled=False,
    layout = widgets.Layout(width='148px !important')
)

show_existence_line_checkbox = widgets.Checkbox(
    value=False,
    description='Show existence line',
    disabled=False,
    layout = widgets.Layout(width='148px !important')
)

show_transition_limits_checkbox.observe(show_limits, 'value')
show_transition_line_checkbox.observe(show_limits, 'value')
show_existence_limits_checkbox.observe(show_limits, 'value')
show_existence_line_checkbox.observe(show_limits, 'value')

isotherm_type_block = widgets.HBox(
    [],
    layout = widgets.Layout(
        align_self='center',
        margin='10px 0 0 0'
    )
)

top_block_111_000.children = [
    change_view_button,
    widgets.HTML('</br>'),
    widgets.HBox([
        t_min_input,
        t_max_input,
        num_input,
        generate_button
    ]),
    widgets.HBox([
        theoretical_toggle,
        experimental_toggle,
    ],
        layout = widgets.Layout(
            margin='20px 0 20px 0',
            width='33%')
    ),
    widgets.HBox([
        show_transition_limits_checkbox,
        show_transition_line_checkbox,
        show_existence_limits_checkbox,
        show_existence_line_checkbox
    ],
        layout = widgets.Layout(
            margin='20px 0 20px 0',
            width='100%')
    )
]

#############################
############MIDDLE BLOCK#####
#############################

middle_block_111_000 = widgets.HBox(
    [],
    layout=widgets.Layout(width='100%')
)

T_values = get_t_range(t_min_input, t_max_input, num_input)

############################
#FIGURE
############################

scale_x = bqs.LinearScale(min = 0.45, max = 5.0)
scale_y = bqs.LinearScale(min = 0.0, max = 2.0)

fig_111_000 = bq.Figure(
    title='Van der Waals isotherms',
    marks=[],
    axes=[],
    animation_duration=0,
    layout = widgets.Layout(
        align_self='center',
        width='75%'
    ),
    legend_location='top-right',
    background_style= {'fill': 'white','stroke': 'black'},
    fig_margin=dict(top=80,bottom=80,left=60,right=30),
    toolbar = True
    )

axis_x = bqa.Axis(
    scale=scale_x,
    tick_format='0.2f',
    tick_style={'font-size': '15px'},
    tick_values=[1.0, 2.0, 3.0, 4.0, 5.0],
    grid_lines = 'none',
    grid_color = '#8e8e8e',
    label='v',
    label_location='middle',
    label_style={'stroke': 'black', 'default-size': 35},
    label_offset='50px'
)

axis_y = bqa.Axis(
    scale=scale_y,
    tick_format='0.2f',
    tick_style={'font-size': '15px'},
    tick_values=[0.0, 1.0, 2.0],
    grid_lines = 'none',
    grid_color = '#8e8e8e',
    orientation='vertical',
    label='p',
    label_location='middle',
    label_style={'stroke': 'red', 'default_size': 35},
    label_offset='50px'
)

fig_111_000.axes = [axis_x, axis_y]


tt = bq.Tooltip(
    fields = ['y', 'x'],
    formats = ['.3f', '.3f'],
    labels = ['p', 'v']
)

marks = []

tracer = bqm.Scatter(
    name = 'tracer',
    x = [1.0],
    y = [1.0],
    scales = {'x': scale_x, 'y': scale_y},
    visible = False,
    colors = ['black'],
    names = [],
    labels=['tracer'],
    tooltip = tt
)

marks.append(tracer)

labels_points = bqm.Scatter(
    name = 'labels',
    x = [],
    y = [],
    scales = {'x': scale_x, 'y': scale_y},
    #opacities = [1.0],
    visible = True,
    colors = ['black'],
    names = [],
    labels=['labels'],
    tooltip = tt,
)

marks.append(labels_points)


isobaric = bqm.Lines(
    x = [],
    y = [],
    scales = {'x': scale_x, 'y': scale_y},
    opacities = [1.0, 0.0],
    visible = False,
    colors = ['green'],
    fill_opacities = [0.35],
    fill = 'none',
    labels = ['isobaric'],
)

marks.append(isobaric)

################################
#LEFT BLOCK
################################

left_block_111_000 = widgets.VBox(
    [],
    layout = widgets.Layout(
        width='15%',
        margin = '70px 0 0 0'
    )
)

maxwell_construction_checkbox = widgets.Checkbox(
    value=False,
    description='Maxwell Construction',
    disabled=False
)

maxwell_construction_checkbox.observe(show_maxwell, 'value')

p_slider = widgets.FloatSlider(
    value=1.0,
    min=0.001,
    max=scale_y.max,
    step=0.001,
    description=r'\( p_r \)',
    disabled=True,
    continuous_update=True,
    orientation='vertical',
    readout=True,
    readout_format='.3f',
    layout = widgets.Layout(
        height = '80%',
        margin = '45px 0 0 0'
    )
)

p_slider.observe(update_isobaric, 'value')

shade_areas_checkbox = widgets.Checkbox(
    value=False,
    description='shade areas',
    disabled=True
)

shade_areas_checkbox.observe(shade_areas, 'value')

integral_value_text = widgets.HTML(
    value="",
)

integral_value_left_text = widgets.HTML(
    value="",
)

integral_value_right_text = widgets.HTML(
    value="",
)

fixed_T_button = widgets.Button(
    description='Calculate real isotherm (fixed T)',
    disabled=True,
    button_style='',
    tooltip="Use Maxwell's construction to" \
            "calculate real isotherm (fixed T)",
    layout = {
        'width' : 'auto',
        'height' : 'auto',
    }
)

fixed_T_button.on_click(find_real_isotherm)

fixed_p_button = widgets.Button(
    description='Calculate real isotherm (fixed p)',
    disabled=True,
    button_style='',
    tooltip="Use Maxwell's construction to" \
            "calculate real isotherm (fixed p)",
    layout = {
        'width' : 'auto',
        'height' : 'auto',
    }
)

fixed_p_button.on_click(add_isotherm_mark_fixed_p)

left_block_111_000.children = [
    maxwell_construction_checkbox,
    shade_areas_checkbox,
    widgets.HTML(value="</br>"),
    widgets.HBox([
        widgets.Label(value="$Left:$"),
        integral_value_left_text,
    ]),
    widgets.HBox([
        widgets.Label(value="$Right:$"),
        integral_value_right_text,
    ]),
    widgets.HBox([
        widgets.Label(value="$Sum:$"),
        integral_value_text,
    ]),
    widgets.HTML(value="</br>"),
    fixed_T_button,
    fixed_p_button
]

################################
#RIGHT BLOCK
################################

right_block_111_000 = widgets.VBox(
    [],
    layout = widgets.Layout(
        width='15%',
        margin = '70px 0 0 0'
    )
)

T_slider = widgets.SelectionSlider(
    options=T_values,
    value=T_values[int(T_values.size/2)],
    description=r'\( T_r \)',
    disabled=False,
    continuous_update=True,
    orientation='vertical',
    readout=True,
    layout = widgets.Layout(
        height = '80%',
        margin = '45px 0 0 0'
    )
)

T_slider.observe(select_isotherm, 'value')

select_visible = widgets.SelectMultiple(
    options=[str(t) for t in T_values],
    value=[str(t) for t in T_values],
    rows=10,
    description='',
    disabled=False,
    layout = widgets.Layout(width = '90%')
)

select_visible.observe(show_isotherm, 'value')

show_all_button = widgets.Button(
    description='Show all',
    disabled=False,
    button_style='',
    tooltip='Show all isotherms',
)

show_all_button.on_click(show_all)

opacity_theo_slider = widgets.FloatSlider(
    value=0.2,
    min=0.0,
    max=1.0,
    step=0.05,
    description="",
    disabled=False,
    continuous_update=True,
    orientation='horizontal',
    readout=True,
    layout = widgets.Layout(width = '90%')
)

opacity_theo_slider.observe(select_isotherm, 'value')

opacity_expe_slider = widgets.FloatSlider(
    value=0.2,
    min=0.0,
    max=1.0,
    step=0.05,
    description="",
    disabled=False,
    continuous_update=True,
    orientation='horizontal',
    readout=True,
    layout = widgets.Layout(width = '90%')
)

opacity_expe_slider.observe(select_isotherm, 'value')

tracer_p = widgets.Label(value=" - ")
tracer_p_base = widgets.Label(value="$p =$")

tracer_v = widgets.Label(value=" - ")
tracer_v_base = widgets.Label(value="$v =$")

tracer_T = widgets.Label(value=" - ")
tracer_T_base = widgets.Label(value="$T =$")

right_block_111_000.children = [
    widgets.Label(value="Select visible isotherms:"),
    select_visible,
    show_all_button,
    widgets.Label(
        value="Theoretical opacity:",
        layout=widgets.Layout(
            margin='20px 0 0 0'
        )
    ),
    opacity_theo_slider,
    widgets.Label(
        value="Experimental opacity:",
        layout=widgets.Layout(
            margin='20px 0 0 0'
        )
    ),
    opacity_expe_slider,
    widgets.HBox([
        tracer_p_base,
        tracer_p,
    ]),
    widgets.HBox([
        tracer_v_base,
        tracer_v,
    ]),
    widgets.HBox([
        tracer_T_base,
        tracer_T,
    ]),
]

################################
#tracer_block
################################

tracer_block_111_000 = widgets.VBox(
    [],
    layout=widgets.Layout(
        align_items="center",
        width="100%"
    )
)

show_tracer_checkbox = widgets.Checkbox(
    value=False,
    description='Show tracer',
    disabled=False,
    layout=widgets.Layout(width='40%')
)

show_tracer_checkbox.observe(show_tracer, 'value')


v_slider = widgets.FloatSlider(
    value=1.0,
    min=0.5,
    max=5.0,
    step=0.001,
    description=r'\( v \)',
    disabled=True,
    continuous_update=True,
    orientation='horizontal',
    readout=True,
    readout_format='.2f',
    layout = widgets.Layout(width = '90%')
)

v_slider.observe(update_tracer, 'value')

label_input = widgets.Text(
    value='',
    placeholder="Label:",
    disabled = True,
)

add_label_button = widgets.Button(
        description='Add label',
        disabled=True,
        button_style='',
        tooltip="Add label in tracer's position",
    )

add_label_button.on_click(add_label_button_clicked)

undo_label_button = widgets.Button(
        description='Undo',
        disabled=True,
        button_style='',
        tooltip="Remove last added label",
    )

undo_label_button.on_click(undo_label_button_clicked)

tracer_block_111_000.children = [
    widgets.HBox([
        show_tracer_checkbox,
    ],
        layout=widgets.Layout(
            align_items="center",
            width="70%")
    ),
    v_slider,
    widgets.HBox([
        label_input,
        add_label_button,
        undo_label_button
    ],
        layout=widgets.Layout(
            align_items="center"
        )
    )
]

#############################
#FIGURE BLOCK
#############################
change_isotherms(None)

figure_block_111_000 = widgets.VBox(
    [],
    layout = widgets.Layout(width='70%')
)

prepare_export_fig_111_000_button = widgets.Button(
        description='Export',
        disabled=False,
        button_style='',
        tooltip='',
    )

prepare_export_fig_111_000_button.on_click(prepare_export)

go_to_export_button = widgets.HTML()

figure_block_111_000.children = [
    widgets.HBox([
        p_slider,
        fig_111_000,
        T_slider
    ]),
    widgets.VBox([
        prepare_export_fig_111_000_button,
        go_to_export_button
        ],
        layout=widgets.Layout(
            align_items="center",
            width="100%"
        )
    ),
    tracer_block_111_000
]

middle_block_111_000.children = [
    left_block_111_000,
    figure_block_111_000,
    right_block_111_000
]

############################
########MAIN BLOCK##########
############################

main_block_111_000 = widgets.VBox(
    [],
    layout=widgets.Layout(
        width='100%',
        align_items='center'
    )
)


main_block_111_000.children = [
    top_block_111_000,
    middle_block_111_000,
]

figures = [
    fig_111_000
]

main_block_111_000

p-T plane and Gibbs free energy

Code: #11B-000

File: apps/van_der_waals/p_T_2D.ipynb

Run it online: Binder


The aim of this Notebook is to visualize the Gibbs energy in different points of the p-T plane.

Interface

The main interface (main_block_11B_000) is divided in two VBox containing two bqplot Figures and fig_11B_001 and fig_11B_001.

[1]:
from IPython.display import Image
Image(filename='../../static/images/apps/11B-000_1.png')
[1]:
_images/apps_van_der_waals_p_T_2D_3_0.png

CSS

A custom css file is used to improve the interface of this application. It can be found here.

[2]:
from IPython.display import HTML
display(HTML("<head><link rel='stylesheet' type='text/css' href='./../../static/custom.css'></head>"))
display(HTML("<style>.container { width:100% !important; }</style>"))

Packages

[3]:
from bqplot import *
import bqplot as bq
import bqplot.marks as bqm
import bqplot.scales as bqs
import bqplot.axes as bqa

import ipywidgets as widgets

import urllib.parse
import webbrowser

import sys

Physical functions

This are the functions that have a physical meaning:

  • get_relative_isotherms
  • experimental_isotherms
  • find_real_fixed_T
  • get_roots
  • p_indefinite_integral
  • definite_integral
  • get_gibbs_energy
[4]:
def get_relative_isotherms(v_range, T_range):
    """This function calculates the theoretical p(v, T) plane
        (in reduced coordinates) according to van der Waals
        equation of state from a given range of volumes
        and tenperatures.

    Args:
        v_range: An array containing the values of v
        (in reduced coordinates)for which the isotherms must be
        calculated.\n
        T_range: An array containing the values of T
        (in reduced coordinates)for which the isotherms must be
        calculated.\n


    Returns:
        isotherms: A list consisted of numpy arrays containing the
        pressures of each isotherm.
    """

    isotherms = []

    for T in T_range:
        p_R = []
        for v in v_range:
            val = (8.0/3.0*T/(v - 1.0/3.0) - 3.0/v**2)
            p_R = np.append(p_R, val)

        isotherms.append(p_R)

    return isotherms
[5]:
def experimental_isotherms(p_range, v_range, T_range, fixed_p, fixed_T):
    """This function calculates the experimental p(v, T) plane
        (in reduced coordinates) according to van der Waals
        equation of state for a given range of volumes
        and tenperatures or for a given range of volumes
        and pressures.

    Args:
        p_range: An array containing the values of p
        (in reduced coordinates)for which the isotherms must be
        calculated. Only used if fixed_p == True.\n
        v_range: An array containing the values of v
        (in reduced coordinates)for which the isotherms must be
        calculated.\n
        T_range: An array containing the values of v
        (in reduced coordinates)for which the isotherms must be
        calculated. Only used if fixed_T == True.\n
        fixed_p: Boolean variable which represents if the isotherms
        must be calculated for a given pressures.\n
        fixed_T: Boolean variable which represents if the isotherms
        must be calculated for a given pressures.\n


    Returns:
        expe_data: A list consisted of numpy arrays containing the
        pressures of each theoretical isotherm.\n
        theo_data: A list consisted of numpy arrays containing the
        pressures of each theoretical isotherm.\n
        v_limits: A list consisted of arrays of the volume limits of
        the phase-transition of each subcritical isotherm.\n
        p_limits: A list consisted of arrays of the pressure limits of
        the phase-transition of each subcritical isotherm.\n
        tenperatures: A list consisted of the tenperatures of the
        isotherms.\n
    """

    if fixed_T:

        theo_data = get_relative_isotherms(v_range, T_range)
        expe_data = []

        v_limits = []
        p_limits = []

        p_range = np.linspace(0.001, 1.0, num=10000)
        pressures, v_isobaric_limits = find_real_fixed_T(p_range, T_range)

        for i in range(len(theo_data)):

            p_expe = []

            if i < len(v_isobaric_limits):

                    v_lim = v_isobaric_limits[i]

                    if len(v_lim) > 1: #check if there is only one point
                        for j in range(len(v_range)):

                            if v_range[j] > v_lim[0] and v_range[j] < v_lim[1]:
                                p_expe.append(pressures[i])

                            else:
                                p_expe.append(theo_data[i][j])

                        v_limits = np.append(v_limits, [v_lim[0], v_lim[1]])
                        p_limits = np.append(p_limits, [pressures[i], pressures[i]])

                    else:
                        p_expe = theo_data[i]
                        v_limits = np.append(v_limits, [1.0])
                        p_limits = np.append(p_limits, [1.0])

            else:

                p_expe = theo_data[i]

            expe_data.append(p_expe)

        tenperatures = T_range

        return expe_data, theo_data, p_limits, v_limits, tenperatures

    elif fixed_p:

        tenperatures, v_isobaric_limits = find_real_fixed_p(p_range, T_range)

        theo_data = get_relative_isotherms(v_range, tenperatures)
        expe_data = []

        v_limits = []
        p_limits = []

        for i in range(len(theo_data)):

            p_expe = []

            if i < len(v_isobaric_limits):

                    v_lim = v_isobaric_limits[i]

                    if len(v_lim) > 1: #check if there is only one point

                        for j in range(len(v_range)):

                            if v_range[j] > v_lim[0] and v_range[j] < v_lim[1]:
                                p_expe.append(p_range[i])

                            else:
                                p_expe.append(theo_data[i][j])

                        v_limits = np.append(v_limits, [v_lim[0], v_lim[1]])
                        p_limits = np.append(p_limits, [p_range[i], p_range[i]])

                    else:
                        p_expe = theo_data[i]
                        v_limits = np.append(v_limits, [1.0])
                        p_limits = np.append(p_limits, [1.0])

            else:

                p_expe = theo_data[i]

            expe_data.append(p_expe)

        return expe_data, theo_data, p_limits, v_limits, tenperatures
[6]:
def get_roots(p, T):
    """This function finds the intersection between an isobaric curve
       and Van der Waals equation of state for a given T.\n
       Values of v with no physical meaning are dismissed
       (v < 0 or complex).

    Args:
        p: Pressure of the isobaric curve.\n
        T: Tenperature of the isotherm.\n


    Returns:
        roots_in_range: A sorted list of the volumes in which the
        isobaric curve intersects the isotherm.\n
    """

    roots = np.roots([1.0, - 1.0/3.0*(1.0 + 8.0*T/p), 3.0/p, -1.0/p])
    roots_in_range = []

    for root in roots:

        # A third degree polynomial has 3 complex roots,
        # but we are only interested in the ones which are
        # purely real.

        if np.isreal(root):

            root = np.real(root)

            if root > 0:

                roots_in_range.append(root)

    roots_in_range.sort()

    return roots_in_range
[7]:
def p_indefinite_integral(p_0, v_0, T):
    """This function calculates the indefinite integral between
    a van der Waals isotherm and a isobaric line.

    Args:
        p0: Isobaric line's pressure.\n
        v0: Value of the volume.\n
        T: Value of the tenperature.\n

    Returns:
        integral: Value of the indefinite integral between a
        van der Waals isotherm at T and a isobaric line of p0 at a
        volume v0.\n
    """

    integral = 8.0/3.0 * T *np.log(v_0 - 1.0/3.0) + 3.0/v_0 - p_0*v_0

    return integral
[8]:
def definite_integral(p_0, v_range, T):
    """This function 'p_indefinite_integral' function to calculate
    the definite integral between a van der Waals isotherm and a
    isobaric line.

    Args:
        p0: Isobaric line's pressure.\n
        v_range: Tuple or list consisted of volume limits.\n
        T: Value of the tenperature.\n

    Returns:
        integral: Value of the definite integral between a
        van der Waals isotherm at T and a isobaric line of p0 in a
        volume range v_range.\n
    """

    v_0, v_1 = v_range[0], v_range[1]

    integral = p_indefinite_integral(p_0, v_1, T) - p_indefinite_integral(p_0, v_0, T)

    return integral
[9]:
def find_real_fixed_T(p_values, T_values):
    """This function uses Maxwell's construction to find the
       pressures in which phase transition happens given some
       fixed tenperatures.\n

    Args:
        p_values: List of pressures in which the real isotherm is
        searched.\n
        T_values: List of tenperatures of the isotherms.\n


    Returns:
        pressures: List of pressures in which phase transition
        happens.\n
        v_range: Volume limits of phase transition zones.
    """

    eps = 1e-3

    pressures = []
    v_ranges = []

    for T in T_values:

        if T < 1.0:

            for p in p_values:

                roots = get_roots(p, T)

                if len(roots) == 3:

                    v_range = [roots[0], roots[2]]
                    area = definite_integral(p, v_range, T)

                    if abs(area) < eps:

                        pressures.append(p)
                        v_ranges.append(v_range)

                        break

        elif T == 1.0:

            pressures.append(1.0)
            v_ranges.append([1.0])

    return pressures, v_ranges
[10]:
def get_gibbs_energy(G_1, G_2, G_3, G_4, phi):
    """This function calculates the representation of Gibbs energy
    for a given constants.\n

    G = G_1*phi + G_2*phi**2 + G_3*phi**3 + G_4*phi**4

    Args:
        G_1, G_2, G_3, G_4: Values of the parameters.
        phi: Array consisted of the values where Gibbs energy
        must be calculated.

    Returns:
        gibbs: Array containing the values of Gibbs energy.

    """
    gibbs = []

    for f in phi:
        gibbs = np.append(gibbs, G_1*f + G_2*f**2 + G_3*f**3 + G_4*f**4)

    return gibbs

Main interface

[ ]:
v_values = np.linspace(start=0.4, stop=5, num=1000)
T_values = np.linspace(start=0.85, stop=1.0, num=8)

expe_data, theo_data, trans_pressures, v_limits, trans_tenperatures = experimental_isotherms(
     p_range = [],
     v_range = v_values,
     T_range = T_values,
     fixed_p = False,
     fixed_T = True
)

trans_pressures = np.unique(trans_pressures)

j = 5

scatter_pres = [
    trans_pressures[j]-0.05,
    trans_pressures[j],
    trans_pressures[j]+0.05,
    1.0
]

scatter_tenp = [T_values[j], T_values[j], T_values[j], 1.0]

phi = np.linspace(-12, 5, 500)

scale_x_11B_001 = bqs.LinearScale(min = min(T_values), max = 1.05)
scale_y_11B_001 = bqs.LinearScale(min = min(trans_pressures), max = 1.2)

fig_11B_001 = bq.Figure(
    title='p vs T (fixed v, reduced variables)',
    marks=[],
    axes=[],
    animation_duration=0,
    layout = widgets.Layout(width='100%'),
    legend_location='top-right',
    background_style= {'fill': 'white',  'stroke': 'black'},
    fig_margin=dict(top=80, bottom=80, left=60, right=30),
    toolbar = True,
)

axis_x_11B_001 = bqa.Axis(
    scale=scale_x_11B_001,
    tick_format='0.2f',
    tick_style={'font-size': '15px'},
    tick_values=[0.85, 0.9, 0.95, 1, 1.05],
    grid_lines = 'none',
    grid_color = '#8e8e8e',
    label='T',
    label_location='middle',
    label_style={'stroke': 'black', 'default-size': 35},
    label_offset='50px'
)

axis_y_11B_001 = bqa.Axis(
    scale=scale_y_11B_001,
    tick_format='0.2f',
    tick_style={'font-size': '15px'},
    tick_values=[0.5, 0.85, 1.2],
    grid_lines = 'none',
    grid_color = '#8e8e8e',
    orientation='vertical',
    label='p',
    label_location='middle',
    label_style={'stroke': 'red', 'default_size': 35},
    label_offset='50px'
)

fig_11B_001.axes = [
    axis_x_11B_001,
    axis_y_11B_001
]

pressures_lines = bqm.Lines(
    name = '',
    x = T_values,
    y = trans_pressures,
    scales = {'x': scale_x_11B_001, 'y': scale_y_11B_001},
    visible = True,
    colors = ['black'],
    names = [],
    labels=['labels']
)

critic_point = bqm.Scatter(
    name = '',
    x = [1.0],
    y = [1.0],
    scales = {'x': scale_x_11B_001, 'y': scale_y_11B_001},
    visible = True,
    colors = ['red'],
    names = [],
    labels=[]
)

tt_box = widgets.HBox([])

scatter_points = bqm.Scatter(
    name = '',
    x = scatter_tenp,
    y = scatter_pres,
    scales = {'x': scale_x_11B_001, 'y': scale_y_11B_001},
    visible = True,
    colors = ['red'],
    names = [],
    labels=[],
)

scatter_points.on_hover(hover_handler)

fig_11B_001.marks = [
    pressures_lines,
    scatter_points
]

scale_x_11B_002 = bqs.LinearScale(min = -4, max = 4)
scale_y_11B_002 = bqs.LinearScale()

fig_11B_002 = bq.Figure(
    title='G (T, phi)',
    marks=[],
    axes=[],
    animation_duration=0,
    layout = widgets.Layout(width='100%'),
    legend_location='top-right',
    background_style= {'fill': 'white',  'stroke': 'black'},
    fig_margin=dict(top=80, bottom=80, left=60, right=30),
    toolbar = True
)

axis_x_11B_002 = bqa.Axis(
    scale=scale_x_11B_002,
    tick_format='0.2f',
    tick_style={'font-size': '15px'},
    num_ticks=0,
    grid_lines = 'none',
    grid_color = '#8e8e8e',
    label='phi',
    label_location='middle',
    label_style={'stroke': 'black', 'default-size': 35},
    label_offset='50px'
)

axis_y_11B_002 = bqa.Axis(
    scale=scale_y_11B_002,
    tick_format='0.2f',
    tick_style={'font-size': '15px'},
    num_ticks=0,
    grid_lines = 'none',
    grid_color = '#8e8e8e',
    orientation='vertical',
    label='G',
    label_location='middle',
    label_style={'stroke': 'red', 'default_size': 35},
    label_offset='30px'
)

fig_11B_002.axes = [
    axis_x_11B_002,
    axis_y_11B_002
]

gibbs_lines = bqm.Lines(
    name = '',
    x = phi,
    y = [],
    scales = {'x': scale_x_11B_002, 'y': scale_y_11B_002},
    visible = True,
    colors = ['blue'],
    names = [],
    labels=['']
)

fig_11B_002.marks = [gibbs_lines]

change_view_button = widgets.ToggleButton(
    value=False,
    description='Presentation mode (OFF)',
    disabled=False,
    button_style='',
    tooltip='',
    icon='desktop',
    layout=widgets.Layout(
        width='initial',
        align_self='center'
    )
)

change_view_button.observe(change_view, 'value')

prepare_export_fig_11B_001_button = widgets.Button(
    description='Export',
    disabled=False,
    button_style='',
    tooltip='',
)

prepare_export_fig_11B_001_button.on_click(prepare_export)

prepare_export_fig_11B_002_button = widgets.Button(
    description='Export',
    disabled=False,
    button_style='',
    tooltip='',
)

prepare_export_fig_11B_002_button.on_click(prepare_export)

main_block_11B_000 = widgets.VBox(
    [],
    layout = widgets.Layout(
        align_items='center',
        width='100%'
    )
)

main_block_11B_000.children = [
    change_view_button,
    widgets.HBox([
        widgets.VBox([
            fig_11B_001,
            prepare_export_fig_11B_001_button
        ],
            layout = widgets.Layout(
                align_items='center',
                width='100%'
            )
        ),
        widgets.VBox([
            fig_11B_002,
            prepare_export_fig_11B_002_button
        ],
            layout = widgets.Layout(
                align_items='center',
                width='100%'
            )
        ),
    ],
        layout = widgets.Layout(
                align_items='center',
                width='100%'
            )
    )
]

figures = [
    fig_11B_001,
    fig_11B_002
]

main_block_11B_000

Chemical potential of a van der Waals real gas

Code: #117-000

File: apps/van_der_waals/chemical_potential.ipynb

Run it online: Binder


The aim of this notebook is to show the contruction of the chemical potential based in van der Waals’ isotherms.

Interface

The main interface (main_block_117_000) is divided in five VBox: block_1, block_2, block_3, block_4 and block_5. block_1 contains two bqplot Figures: fig_117_001 and fig_117_002. block_2 contains the T_slider widget which controls the isotherms and chemical potentials shown in fig_117_001, fig_117_002, fig_117_003 and fig_117_004. block_3 contains two bqplot Figures: fig_117_003 and fig_117_004. block_4 contains two bqplot Figures: fig_117_005 and fig_117_006. block_5 contains two bqplot Figures: fig_117_007 and fig_117_008.

[1]:
from IPython.display import Image
Image(filename='../../static/images/apps/117-000_1.png')
[1]:
_images/apps_van_der_waals_chemical_potential_3_0.png
[2]:
Image(filename='../../static/images/apps/117-000_2.png')
[2]:
_images/apps_van_der_waals_chemical_potential_4_0.png
[3]:
Image(filename='../../static/images/apps/117-000_3.png')
[3]:
_images/apps_van_der_waals_chemical_potential_5_0.png
[4]:
Image(filename='../../static/images/apps/117-000_4.png')
[4]:
_images/apps_van_der_waals_chemical_potential_6_0.png

CSS

A custom css file is used to improve the interface of this application. It can be found here.

[5]:
from IPython.display import HTML
display(HTML("<head><link rel='stylesheet' type='text/css' href='./../../static/custom.css'></head>"))
display(HTML("<style>.container { width:100% !important; }</style>"))
display(HTML("<style>.widget-label { display: contents !important; }</style>"))
display(HTML("<style>.slider-container { margin: 12px !important; }</style>"))
display(HTML("<style>.jupyter-widgets { overflow: auto !important; }</style>"))

Packages

[6]:
from bqplot import *
import bqplot as bq
import bqplot.marks as bqm
import bqplot.scales as bqs
import bqplot.axes as bqa

import ipywidgets as widgets

import urllib.parse
import webbrowser

import sys

Physical functions

This are the functions that have a physical meaning:

  • get_relative_isotherms
  • experimetal_isotherms
  • get_roots
  • p_indefinite_integral
  • p_definite_integral
  • find_real_fixed_p
  • find_real_fixed_T
  • get_chemical_potential
[7]:
def get_relative_isotherms(v_range, T_range):
    """This function calculates the theoretical p(v, T) plane
        (in reduced coordinates) according to van der Waals
        equation of state from a given range of volumes
        and tenperatures.

    Args:
        v_range: An array containing the values of v
        (in reduced coordinates)for which the isotherms must be
        calculated.\n
        T_range: An array containing the values of v
        (in reduced coordinates)for which the isotherms must be
        calculated.\n


    Returns:
        isotherms: A list consisted of numpy arrays containing the
        pressures of each isotherm.
    """

    isotherms = []

    for T in T_range:
        p_R = []
        for v in v_range:
            val = (8.0/3.0*T/(v - 1.0/3.0) - 3.0/v**2)
            p_R = np.append(p_R, val)

        isotherms.append(p_R)

    return isotherms

    return isotherms
[8]:
def experimental_isotherms(p_range, v_range, T_range, fixed_p, fixed_T):
    """This function calculates the experimental p(v, T) plane
        (in reduced coordinates) according to van der Waals
        equation of state for a given range of volumes
        and tenperatures or for a given range of volumes
        and pressures.

    Args:
        p_range: An array containing the values of p
        (in reduced coordinates)for which the isotherms must be
        calculated. Only used if fixed_p == True.\n
        v_range: An array containing the values of v
        (in reduced coordinates)for which the isotherms must be
        calculated.\n
        T_range: An array containing the values of v
        (in reduced coordinates)for which the isotherms must be
        calculated. Only used if fixed_T == True.\n
        fixed_p: Boolean variable which represents if the isotherms
        must be calculated for a given pressures.\n
        fixed_T: Boolean variable which represents if the isotherms
        must be calculated for a given pressures.\n


    Returns:
        expe_data: A list consisted of numpy arrays containing the
        pressures of each theoretical isotherm.\n
        theo_data: A list consisted of numpy arrays containing the
        pressures of each theoretical isotherm.\n
        v_limits: A list consisted of arrays of the volume limits of
        the phase-transition of each subcritical isotherm.\n
        p_limits: A list consisted of arrays of the pressure limits of
        the phase-transition of each subcritical isotherm.\n
        tenperatures: A list consisted of the tenperatures of the
        isotherms.\n
    """

    if fixed_T:

        theo_data = get_relative_isotherms(v_range, T_range)
        expe_data = []

        v_limits = []
        p_limits = []

        p_range = np.linspace(0.001, 1.0, num=10000)
        pressures, v_isobaric_limits = find_real_fixed_T(p_range, T_range)

        for i in range(len(theo_data)):

            p_expe = []

            if i < len(v_isobaric_limits):

                    v_lim = v_isobaric_limits[i]

                    if len(v_lim) > 1: #check if there is only one point
                        for j in range(len(v_range)):

                            if v_range[j] > v_lim[0] and v_range[j] < v_lim[1]:
                                p_expe.append(pressures[i])

                            else:
                                p_expe.append(theo_data[i][j])

                        v_limits = np.append(v_limits, [v_lim[0], v_lim[1]])
                        p_limits = np.append(p_limits, [pressures[i], pressures[i]])

                    else:
                        p_expe = theo_data[i]
                        v_limits = np.append(v_limits, [1.0])
                        p_limits = np.append(p_limits, [1.0])

            else:

                p_expe = theo_data[i]

            expe_data.append(p_expe)

        tenperatures = T_range

        return expe_data, theo_data, p_limits, v_limits, tenperatures

    elif fixed_p:

        tenperatures, v_isobaric_limits = find_real_fixed_p(p_range, T_range)

        theo_data = get_relative_isotherms(v_range, tenperatures)
        expe_data = []

        v_limits = []
        p_limits = []

        for i in range(len(theo_data)):

            p_expe = []

            if i < len(v_isobaric_limits):

                    v_lim = v_isobaric_limits[i]

                    if len(v_lim) > 1: #check if there is only one point

                        for j in range(len(v_range)):

                            if v_range[j] > v_lim[0] and v_range[j] < v_lim[1]:
                                p_expe.append(p_range[i])

                            else:
                                p_expe.append(theo_data[i][j])

                        v_limits = np.append(v_limits, [v_lim[0], v_lim[1]])
                        p_limits = np.append(p_limits, [p_range[i], p_range[i]])

                    else:
                        p_expe = theo_data[i]
                        v_limits = np.append(v_limits, [1.0])
                        p_limits = np.append(p_limits, [1.0])

            else:

                p_expe = theo_data[i]

            expe_data.append(p_expe)

        return expe_data, theo_data, p_limits, v_limits, tenperatures
[9]:
def get_roots(p, T):
    """This function finds the intersection between an isobaric curve
       and Van der Waals equation of state for a given T.\n
       Values of v with no physical meaning are dismissed
       (v < 0 or complex).

    Args:
        p: Pressure of the isobaric curve.\n
        T: Tenperature of the isotherm.\n


    Returns:
        roots_in_range: A sorted list of the volumes in which the
        isobaric curve intersects the isotherm.\n
    """

    roots = np.roots([1.0, - 1.0/3.0*(1.0 + 8.0*T/p), 3.0/p, -1.0/p])
    roots_in_range = []

    for root in roots:

        # A third degree polynomial has 3 complex roots,
        # but we are only interested in the ones which are
        # purely real.

        if np.isreal(root):

            root = np.real(root)

            if root > 0:

                roots_in_range.append(root)

    roots_in_range.sort()

    return roots_in_range
[10]:
def p_indefinite_integral(p_0, v_0, T):
    """This function calculates the indefinite integral between
    a van der Waals isotherm and a isobaric line.

    Args:
        p0: Isobaric line's pressure.\n
        v0: Value of the volume.\n
        T: Value of the tenperature.\n

    Returns:
        integral: Value of the indefinite integral between a
        van der Waals isotherm at T and a isobaric line of p0 at a
        volume v0.\n
    """

    integral = 8.0/3.0 * T *np.log(v_0 - 1.0/3.0) + 3.0/v_0 - p_0*v_0

    return integral
[11]:
def definite_integral(p_0, v_range, T):
    """This function 'p_indefinite_integral' function to calculate
    the definite integral between a van der Waals isotherm and a
    isobaric line.

    Args:
        p0: Isobaric line's pressure.\n
        v_range: Tuple or list consisted of volume limits.\n
        T: Value of the tenperature.\n

    Returns:
        integral: Value of the definite integral between a
        van der Waals isotherm at T and a isobaric line of p0 in a
        volume range v_range.\n
    """

    v_0, v_1 = v_range[0], v_range[1]

    integral = p_indefinite_integral(p_0, v_1, T) - p_indefinite_integral(p_0, v_0, T)

    return integral
[12]:
def find_real_fixed_T(p_values, T_values):
    """This function uses Maxwell's construction to find the
       pressures in which phase transition happens given some
       fixed tenperatures.\n

    Args:
        p_values: List of pressures in which the real isotherm is
        searched.\n
        T_values: List of tenperatures of the isotherms.\n


    Returns:
        pressures: List of pressures in which phase transition
        happens.\n
        v_range: Volume limits of phase transition zones.
    """

    eps = 1e-3

    pressures = []
    v_ranges = []

    for T in T_values:

        if T < 1.0:

            for p in p_values:

                roots = get_roots(p, T)

                if len(roots) == 3:

                    v_range = [roots[0], roots[2]]
                    area = definite_integral(p, v_range, T)

                    if abs(area) < eps:

                        pressures.append(p)
                        v_ranges.append(v_range)

                        break

        elif T == 1.0:

            pressures.append(1.0)
            v_ranges.append([1.0])

    return pressures, v_ranges
[13]:
def get_chemical_potential(p_values, v_values):
    """This function calculates chemical potential by integrating
    v(p) isotherms.\n

    Args:
        p_values: List of numpy arrays containing the pressures of
        the isotherms.\n
        v_values: List of numpy arrays containing the volumes of
        the isotherms.\n


    Returns:
        mu: List of numpy arrays containing the chemical potentials of
        the isotherms.
    """

    mu = []


    for i in range(len(v_values)):

        v = v_values[i]
        p = p_values[i]

        pot = [10.0] #starting random value

        l = np.size(p)

        for j in range(1, l):
            pot.append(pot[j-1] + v[j]*(p[j] - p[j-1]))

        mu.append(pot)

    return mu

Main interface

[31]:
#v_values_1 = np.geomspace(0.48, 0.8, 1000)
#v_values_2 = np.geomspace(0.8, 5.2, 500)
#
#v_values = np.concatenate((v_values_1, v_values_2))
v_values = np.geomspace(0.48, 5.0, 1000)
T_values = np.round(np.linspace(0.85, 1.1, 15), 2)

if 1.0 not in T_values:
    T_values = np.sort(np.append(T_values, 1.0))

data = experimental_isotherms(
        p_range=[],
        v_range=v_values,
        T_range=T_values,
        fixed_T = True,
        fixed_p = False
)

expe_p_values = data[0]
theo_p_values = data[1]

p_limits = data[2]
v_limits = data[3]
T_limits = data[4]

colors = generate_gradient('#FF0000', '#FFfa00', len(T_limits))
opacities = [0.0 for t in T_values]
opacities[0] = 1.0

# change view button

change_view_button = widgets.ToggleButton(
    value=False,
    description='Presentation mode (OFF)',
    disabled=False,
    button_style='',
    tooltip='',
    icon='desktop',
    layout=widgets.Layout(
        width='auto'
    )
)

change_view_button.observe(change_view, 'value')
[32]:
################################
######1ST BLOCK#################
################################

# This block shows the p(v,T) (fig_117_001) and
# v(p,T) (fig_117_002) figures.


block_1 = widgets.VBox(
    [],
    layout=widgets.Layout(
        align_items='center',
        align_self= 'center'
    )
)

scale_x = bqs.LinearScale(min = 0.4, max = 5.0)
scale_y = bqs.LinearScale(min = 0.0, max = 2.0)

axis_x_001 = bqa.Axis(
    scale=scale_x,
    tick_format='.2f',
    tick_style={'font-size': '15px'},
    tick_values = [1.0, 2.0, 3.0, 4.0, 5.0],
    grid_lines = 'none',
    grid_color = '#8e8e8e',
    label='v',
    label_location='middle',
    label_style={'stroke': 'black', 'default-size': 35},
    label_offset='50px'
)

axis_y_001 = bqa.Axis(
    scale=scale_y,
    tick_format='.1f',
    tick_style={'font-size': '15px'},
    tick_values = [0, 1.0, 2.0],
    grid_lines = 'none',
    grid_color = '#8e8e8e',
    orientation='vertical',
    label='p',
    label_location='middle',
    label_style={'stroke': 'red', 'default_size': 35},
    label_offset='50px'
)

fig_117_001 = Figure(
    title='p vs v (fixed T, reduced variables)',
    marks=[],
    axes=[axis_x_001, axis_y_001],
    animation_duration=250,
    legend_location='top-right',
    background_style= {'fill': 'white',  'stroke': 'black'},
    min_aspect_ratio=1.0,
    max_aspect_ratio=1.0,
    fig_margin=dict(top=80, bottom=60, left=70, right=20),
    toolbar = True
)

lines_117_001 = bqm.Lines(
    x = v_values,
    y = np.array(theo_p_values),
    scales = {'x': scale_x, 'y': scale_y},
    opacities = opacities,
    visible = True,
    colors = colors,
)

fig_117_001.marks = [lines_117_001]

axis_x_002 = bqa.Axis(
    scale=scale_y,
    tick_format='.1f',
    tick_style={'font-size': '15px'},
    tick_values = [0, 1.0, 2.0],
    grid_lines = 'none',
    grid_color = '#8e8e8e',
    label='p',
    label_location='middle',
    label_style={'stroke': 'black', 'default-size': 35},
    label_offset='50px'
)

axis_y_002 = bqa.Axis(
    scale=scale_x,
    tick_format='.2f',
    tick_style={'font-size': '15px'},
    tick_values = [1.0, 2.0, 3.0, 4.0, 5.0],
    grid_lines = 'none',
    grid_color = '#8e8e8e',
    orientation='vertical',
    label='v',
    label_location='middle',
    label_style={'stroke': 'red', 'default_size': 35},
    label_offset='50px'
)

fig_117_002 = Figure(
    title='v vs p (fixed T, reduced variables)',
    marks=[],
    axes=[axis_x_002, axis_y_002],
    animation_duration=250,
    legend_location='top-right',
    background_style= {'fill': 'white',  'stroke': 'black'},
    min_aspect_ratio=1.0,
    max_aspect_ratio=1.0,
    fig_margin=dict(top=80, bottom=60, left=70, right=20),
    toolbar = True,
)

lines_117_002 = bqm.Lines(
    x = np.array(theo_p_values),
    y = np.array([v_values for p in theo_p_values]),
    scales = {'x': scale_y, 'y': scale_x},
    opacities = opacities,
    visible = True,
    colors = colors
)

fig_117_002.marks = [lines_117_002]

# Export buttons

prepare_export_fig_117_001_button = widgets.Button(
    description='Export',
    disabled=False,
    button_style='',
    tooltip='',
)

prepare_export_fig_117_001_button.on_click(prepare_export)

prepare_export_fig_117_002_button = widgets.Button(
    description='Export',
    disabled=False,
    button_style='',
    tooltip='',
)

prepare_export_fig_117_002_button.on_click(prepare_export)


block_1.children = [
    widgets.HBox([
        widgets.VBox([
            fig_117_001,
            prepare_export_fig_117_001_button
        ],
        layout=widgets.Layout(
            align_items='center'
        )),
        widgets.VBox([
            fig_117_002,
            prepare_export_fig_117_002_button
        ],
        layout=widgets.Layout(
            align_items='center'
        )),
    ],
        layout=widgets.Layout(
            width='100%',
            align_items='center'
        )
    )
]
[33]:
################################
######2ND BLOCK#################
################################

# This block shows the slider to control the tenperature
# (T_slider).

block_2 = widgets.VBox(
    [],
    layout=widgets.Layout(align_items='center')
)

T_slider = widgets.SelectionSlider(
    options= T_values,
    value=T_values[0],
    description=r'\( T \)',
    disabled=False,
    continuous_update=True,
    orientation='horizontal',
    readout=True,
    layout = widgets.Layout(
        width = '300px',
        height = 'auto',
        margin='0 0 0 50px'
    )
)

T_slider.observe(change_tenperature, 'value')

block_2.children = [T_slider]
[34]:
################################
######3RD BLOCK#################
################################

# This block shows the v(p,T) (fig_117_003) and
# mu(p,T) (fig_117_004) figures.

fig_117_003 = Figure(
    title='v vs p (fixed T, reduced variables)',
    marks=[],
    axes=[axis_x_002, axis_y_002],
    animation_duration=0, #500,
    legend_location='top-right',
    background_style= {'fill': 'white',  'stroke': 'black'},
    min_aspect_ratio=1.0,
    max_aspect_ratio=1.0,
    fig_margin=dict(top=80, bottom=60, left=70, right=20),
    toolbar = True,
)

area_117_003 = bqm.Lines(
    x = [],
    y = [],
    scales = {'x': scale_y, 'y': scale_x},
    opacities = [1.0],
    visible = True,
    colors = ['#39362d'],
    fill = 'bottom',
    fill_colors = ['#ffd429'],
    fill_opacities = [0.4]
)

tracer_117_003 = bqm.Scatter(
    name = '',
    x = [0.0],
    y = [0.0],
    scales = {'x': scale_y, 'y': scale_x},
    opacity = [1.0, 0.0],
    visible = False,
    colors = ['#2807a3'],
)

tt_003 = bq.Tooltip(
    fields = ['x', 'y'],
    formats = ['.3f', '.3f'],
    labels = ['v', 'p']
)

labels_117_003 = bqm.Scatter(
    name = 'labels',
    x = [],
    y = [],
    scales = {'x': scale_y, 'y': scale_x},
    #opacities = [1.0],
    visible = True,
    colors = ['black'],
    names = [],
    labels=['labels'],
    tooltip = tt_003,
)

fig_117_003.marks = [
    lines_117_002,
    area_117_003,
    tracer_117_003,
    labels_117_003
]

theo_p_values_inverted = []
theo_v_values_inverted = []

for p_values in theo_p_values:

    indexes = np.where(p_values < 2.2)

    theo_p_values_inverted.append(
        np.flip(np.take(p_values, indexes[0]))
    )

    theo_v_values_inverted.append(
        np.flip(np.take(v_values, indexes[0]))
    )

mu = get_chemical_potential(
    theo_p_values_inverted,
    theo_v_values_inverted
)

for i in range(len(mu)):
    mu[i] = np.array(mu[i]) - 10.0*T_slider.options[i]

p_text = widgets.HTML(
    value='<p> {:.3f} </p>'.format(theo_p_values_inverted[T_slider.index][i]),
    layout = widgets.Layout(height='auto', margin='10px 0 0 10px')
)

p_slider = widgets.SelectionSlider(
    options= theo_v_values_inverted[0],
    value=theo_v_values_inverted[0][0],
    description=r'\( p \)',
    disabled=False,
    continuous_update=True,
    orientation='horizontal',
    readout=False,
    layout = widgets.Layout(
        width = '60%',
        height = 'auto',
        margin='0 0 0 50px'
    )
)

p_slider.observe(update_tracer, 'value')
p_slider.observe(update_text, 'value')


# Figure fig_117_004

scale_x_004 = bqs.LinearScale(min = 0.0, max = 2.0)
scale_y_004 = bqs.LinearScale(
    min = min(mu[T_slider.index]),
    max = max(mu[T_slider.index])
)

axis_x_004 = bqa.Axis(
    scale=scale_x_004,
    tick_format='.2f',
    tick_style={'font-size': '15px'},
    tick_values = [0.0, 1.0, 2.0],
    grid_lines = 'none',
    grid_color = '#8e8e8e',
    label='p',
    label_location='middle',
    label_style={'stroke': 'black', 'default-size': 35},
    label_offset='50px'
)

axis_y_004 = bqa.Axis(
    scale=scale_y_004,
    tick_format='.2f',
    tick_style={'font-size': '15px'},
    num_ticks = 0,
    grid_lines = 'none',
    grid_color = '#8e8e8e',
    orientation='vertical',
    label='mu',
    label_location='middle',
    label_style={'stroke': 'red', 'default_size': 35},
    label_offset='50px'
)

fig_117_004 = Figure(
    title='mu vs p (fixed T)',
    marks=[],
    axes=[axis_x_004, axis_y_004],
    animation_duration=0,
    legend_location='top-right',
    background_style= {'fill': 'white',  'stroke': 'black'},
    min_aspect_ratio=1.0,
    max_aspect_ratio=1.0,
    fig_margin=dict(top=80, bottom=60, left=70, right=20),
    toolbar = True,
)

lines_117_004 = bqm.Lines(
    x = [],
    y = [],
    scales = {'x': scale_x_004, 'y': scale_y_004},
    opacities = [1.0],
    visible = True,
    colors = colors,
)

tracer_117_004 = bqm.Scatter(
    name = '',
    x = [0.0],
    y = [0.0],
    scales = {'x': scale_x_004, 'y': scale_y_004},
    opacity = [1.0, 0.0],
    visible = True,
    colors = ['#2807a3'],
)

labels_117_004 = bqm.Scatter(
    name = 'labels',
    x = [],
    y = [],
    scales = {'x': scale_x_004, 'y': scale_y_004},
    #opacities = [1.0],
    visible = True,
    colors = ['black'],
    names = [],
    labels=['labels'],
)

fig_117_004.marks = [
    lines_117_004,
    tracer_117_004,
    labels_117_004,
]

restart_button = widgets.Button(
    description='Clean figure',
    disabled=False,
    button_style='',
    tooltip="",
    layout = widgets.Layout(height='auto')
)

restart_button.on_click(restart_chemical_potential)


show_all_button = widgets.Button(
    description='Show all potentials',
    disabled=False,
    button_style='',
    tooltip="",
    layout = widgets.Layout(height='auto')
)

show_all_button.on_click(show_all_potentials)


# Label buttons

label_input_117_003 = widgets.Text(
    value='',
    placeholder="Label:",
    disabled = False,
)

add_label_button_117_003 = widgets.Button(
    description='Add label',
    disabled=False,
    button_style='',
    tooltip="Add label in tracer's position",
)

add_label_button_117_003.on_click(add_label_button_clicked)

undo_label_button_117_003 = widgets.Button(
    description='Undo',
    disabled=False,
    button_style='',
    tooltip="Remove last added label",
)

undo_label_button_117_003.on_click(undo_label_button_clicked)

label_input_117_004 = widgets.Text(
    value='',
    placeholder="Label:",
    disabled = False,
)

add_label_button_117_004 = widgets.Button(
    description='Add label',
    disabled=False,
    button_style='',
    tooltip="Add label in tracer's position",
)

add_label_button_117_004.on_click(add_label_button_clicked)

undo_label_button_117_004 = widgets.Button(
    description='Undo',
    disabled=False,
    button_style='',
    tooltip="Remove last added label",
)

undo_label_button_117_004.on_click(undo_label_button_clicked)

# Export buttons

prepare_export_fig_117_003_button = widgets.Button(
    description='Export',
    disabled=False,
    button_style='',
    tooltip='',
    layout=widgets.Layout(align_self='center')
)

prepare_export_fig_117_003_button.on_click(prepare_export)

prepare_export_fig_117_004_button = widgets.Button(
    description='Export',
    disabled=False,
    button_style='',
    tooltip='',
)

prepare_export_fig_117_004_button.on_click(prepare_export)


block_3 = widgets.VBox(
    [],
    layout=widgets.Layout(align_items='center')
)

block_3.children = [
    widgets.HBox([
        widgets.VBox([
            fig_117_003,
            prepare_export_fig_117_003_button,
            widgets.HBox([p_slider, p_text])],
            layout=widgets.Layout(
                height='auto',
            )),
        widgets.VBox([
            fig_117_004,
            prepare_export_fig_117_004_button,
            widgets.HBox([restart_button, show_all_button]),
        ],
            layout=widgets.Layout(align_items='center')
        )
    ]),
    widgets.HBox([
        label_input_117_003,
        add_label_button_117_003,
        undo_label_button_117_003,
        widgets.HTML('<div style="width:10px"></div>'),
        label_input_117_004,
        add_label_button_117_004,
        undo_label_button_117_004
    ]),
]
[35]:
################################
######4TH BLOCK#################
################################

block_4 = widgets.HBox(
    [],
    layout=widgets.Layout(
        align_items='center'
    )
)

tt_005 = bq.Tooltip(
    fields = ['y', 'x'],
    formats = ['.3f', '.3f'],
    labels = ['p', 'v']
)

lines_117_005 = bqm.Lines(
    x = v_values,
    y = theo_p_values,
    scales = {'x': scale_x, 'y': scale_y},
    opacities = [1.0],
    visible = True,
    colors = colors,
)

scatter_117_005 = bqm.Scatter(
    name = '',
    x = v_limits,
    y = p_limits,
    scales = {'x': scale_x, 'y': scale_y},
    visible = True,
    colors = ['black'],
    default_size = 15,
    tooltip = tt_005
)

fig_117_005 = Figure(
    title='v vs p (fixed T, reduced variables)',
    marks=[lines_117_005, scatter_117_005],
    axes=[axis_x_001, axis_y_001],
    animation_duration=0, #500,
    legend_location='top-right',
    background_style= {'fill': 'white',  'stroke': 'black'},
    min_aspect_ratio=1.0,
    max_aspect_ratio=1.0,
    fig_margin=dict(top=80, bottom=60, left=70, right=20),
    toolbar = True,
)

# fig_117_006

# Calculate the limits of the plot
max_limit = 0.0
min_limit = 100.0

for pot in mu:
    max_pot = max(pot)
    min_pot = min(pot)

    if max_pot > max_limit:
        max_limit = max_pot

    if min_pot < min_limit:
        min_limit = min_pot

# Calculate the phase transition points in the mu(p, T) plane
trans_mu = []

trans_p = np.unique(p_limits)

for i in range(len(trans_p)):

    p = trans_p[i]
    m = mu[i]

    j = find_nearest_index(theo_p_values_inverted[i], p)

    trans_mu.append(mu[i][j])

scale_x_006 = bqs.LinearScale(min = 0.0, max = 2.0)
scale_y_006 = bqs.LinearScale(min = min_limit, max = max_limit)

axis_x_006 = bqa.Axis(
    scale=scale_x_006,
    tick_format='.2f',
    tick_style={'font-size': '15px'},
    tick_values = [0.0, 1.0, 2.0],
    grid_lines = 'none',
    grid_color = '#8e8e8e',
    label='p',
    label_location='middle',
    label_style={'stroke': 'black', 'default-size': 35},
    label_offset='50px'
)

axis_y_006 = bqa.Axis(
    scale=scale_y_006,
    tick_format='.2f',
    tick_style={'font-size': '15px'},
    num_ticks = 5,
    grid_lines = 'none',
    grid_color = '#8e8e8e',
    orientation='vertical',
    label='mu',
    label_location='middle',
    label_style={'stroke': 'red', 'default_size': 35},
    label_offset='50px'
)


lines_117_006 = bqm.Lines(
    x = [p.tolist() for p in theo_p_values_inverted],
    y = [m.tolist() for m in mu],
    scales = {'x': scale_x_006, 'y': scale_y_006},
    opacities = [1.0],
    visible = True,
    colors = colors,
)

scatter_117_006 = bqm.Scatter(
    name = '',
    x = trans_p,
    y = trans_mu,
    scales = {'x': scale_x_006, 'y': scale_y_006},
    visible = True,
    colors = ['black'],
    default_size = 15,
)

fig_117_006 = Figure(
    title='mu vs p (fixed T)',
    marks=[lines_117_006, scatter_117_006],
    axes=[axis_x_006, axis_y_006],
    animation_duration=0,
    legend_location='top-right',
    background_style= {'fill': 'white',  'stroke': 'black'},
    min_aspect_ratio=1.0,
    max_aspect_ratio=1.0,
    fig_margin=dict(top=80, bottom=60, left=70, right=20),
    toolbar = True,
)

# Export buttons

prepare_export_fig_117_005_button = widgets.Button(
    description='Export',
    disabled=False,
    button_style='',
    tooltip='',
    layout=widgets.Layout(align_self='center')
)

prepare_export_fig_117_005_button.on_click(prepare_export)

prepare_export_fig_117_006_button = widgets.Button(
    description='Export',
    disabled=False,
    button_style='',
    tooltip='',
)

prepare_export_fig_117_006_button.on_click(prepare_export)


block_4.children = [
    widgets.HBox([
        widgets.VBox([
            fig_117_005,
            prepare_export_fig_117_005_button
        ],
        layout=widgets.Layout(
            align_items='center'
        )),
        widgets.VBox([
            fig_117_006,
            prepare_export_fig_117_006_button
        ],
        layout=widgets.Layout(
            align_items='center'
        )),
    ],
        layout=widgets.Layout(
            width='100%',
            align_items='center'
        )
    )
]
[36]:
################################
######5TH BLOCK#################
################################

# This block contains the p(T) and T(p) figures

block_5 = widgets.HBox(
    [],
    layout=widgets.Layout(align_items='center')
)

scale_y_007 = bqs.LinearScale(
    min = min(T_values),
    max = max(T_values)
)

axis_y_007 = bqa.Axis(
    scale=scale_y_007,
    tick_format='.2f',
    tick_style={'font-size': '15px'},
    tick_values = [0.0, 1.0, 2.0],
    grid_lines = 'none',
    grid_color = '#8e8e8e',
    orientation='vertical',
    label='T',
    label_location='middle',
    label_style={'stroke': 'red', 'default_size': 35},
    label_offset='50px',
)

tt_007 = bq.Tooltip(
    fields = ['y', 'x'],
    formats = ['.3f', '.3f'],
    labels = ['T', 'p']
)

lines_117_007 = bqm.Lines(
    x = trans_p,
    y = T_values,
    scales = {'x': scale_x_006, 'y': scale_y_007},
    opacities = [1.0],
    visible = True,
    colors = ['red'],
)

scatter_117_007 = bqm.Scatter(
    name = '',
    x = trans_p,
    y = T_values,
    scales = {'x': scale_x_006, 'y': scale_y_007},
    visible = True,
    colors = ['black'],
    default_size = 15,
    tooltip = tt_007
)

fig_117_007 = Figure(
    title='T vs p (fixed v, reduced variables)',
    marks=[lines_117_007, scatter_117_007],
    axes=[axis_x_006, axis_y_007],
    animation_duration=0,
    legend_location='top-right',
    background_style= {'fill': 'white',  'stroke': 'black'},
    min_aspect_ratio=1.0,
    max_aspect_ratio=1.0,
    fig_margin=dict(top=80, bottom=60, left=70, right=20),
    toolbar = True,
)

# fig_117_008

scale_y_008 = bqs.LinearScale(
    min = min(T_values),
    max = max(T_values),
    reverse = True
)

axis_x_008 = bqa.Axis(
    scale=scale_x_006,
    tick_format='.2f',
    tick_style={'font-size': '15px'},
    tick_values = [0.0, 1.0, 2.0],
    grid_lines = 'none',
    grid_color = '#8e8e8e',
    label='p',
    label_location='middle',
    label_style={'stroke': 'black', 'default-size': 35},
    label_offset='30px',
    side='top'
)

axis_y_008 = bqa.Axis(
    scale=scale_y_008,
    tick_format='.2f',
    tick_style={'font-size': '15px'},
    tick_values = [0.0, 1.0, 2.0],
    grid_lines = 'none',
    grid_color = '#8e8e8e',
    orientation='vertical',
    label='T',
    label_location='middle',
    label_style={'stroke': 'red', 'default_size': 35},
    label_offset='50px',
)

tt_008 = bq.Tooltip(
    fields = ['y', 'x'],
    formats = ['.3f', '.3f'],
    labels = ['T', 'p']
)

lines_117_008 = bqm.Lines(
    x = trans_p,
    y = T_values,
    scales = {'x': scale_x_006, 'y': scale_y_008},
    opacities = [1.0],
    visible = True,
    colors = ['red'],
)

scatter_117_008 = bqm.Scatter(
    name = '',
    x = trans_p,
    y = T_values,
    scales = {'x': scale_x_006, 'y': scale_y_008},
    visible = True,
    colors = ['black'],
    default_size = 15,
    tooltip = tt_008
)

fig_117_008 = Figure(
    title='T vs p (fixed v, reduced variables)',
    marks=[lines_117_008, scatter_117_008],
    axes=[axis_x_008, axis_y_008],
    animation_duration=0,
    legend_location='top-right',
    background_style= {'fill': 'white',  'stroke': 'black'},
    min_aspect_ratio=1.0,
    max_aspect_ratio=1.0,
    fig_margin=dict(top=160, bottom=60, left=70, right=20),
    toolbar = True,
)

# Export buttons

prepare_export_fig_117_007_button = widgets.Button(
    description='Export',
    disabled=False,
    button_style='',
    tooltip='',
    layout=widgets.Layout(align_self='center')
)

prepare_export_fig_117_007_button.on_click(prepare_export)

prepare_export_fig_117_008_button = widgets.Button(
    description='Export',
    disabled=False,
    button_style='',
    tooltip='',
)

prepare_export_fig_117_008_button.on_click(prepare_export)

block_5.children = [
    widgets.HBox([
        widgets.VBox([
            fig_117_007,
            prepare_export_fig_117_007_button
        ],
        layout=widgets.Layout(
            align_items='center'
        )),
        widgets.VBox([
            fig_117_008,
            prepare_export_fig_117_008_button
        ],
        layout=widgets.Layout(
            align_items='center'
        )),
    ],
        layout=widgets.Layout(
            width='100%',
            align_items='center'
        )
    )
]
[ ]:
###################################
#############MAIN BLOCK############
###################################

main_block_117_000 = widgets.VBox(
    [],
    layout=widgets.Layout(align_items='center')
)

main_block_117_000.children = [
    change_view_button,
    block_1,
    block_2,
    block_3,
    block_4,
    block_5
]

figures = [
    fig_117_001,
    fig_117_002,
    fig_117_003,
    fig_117_004,
    fig_117_005,
    fig_117_006,
    fig_117_007,
    fig_117_008,
]

main_block_117_000

Stability condition on van der Waals isotherms

Code: #11A-000

File: apps/van_der_waals/stability.ipynb

Run it online: Binder


The aim of this notebook is to visualize the \(\left(\frac{\partial p}{\partial v}\right)_{T,N} < 0\) stability condition on van der Waals isotherms.

Interface

The main interface (main_block_11A_000) is divided in two HBox: top_block_11A_000 and bottom_block_11A_000. bottom_block_11A_000 contains of 2 bqplot Figures: fig_11A_001 and fig_11A_002.

[1]:
from IPython.display import Image
Image(filename='../../static/images/apps/11A-000_1.png')
[1]:
_images/apps_van_der_waals_stability_4_0.png

The slider T_slider updates the values of \(T\) which updates the lines of fig_11A_001 and fig_11A_002.

[2]:
Image(filename='../../static/images/apps/11A-000_2.png')
[2]:
_images/apps_van_der_waals_stability_6_0.png

CSS

A custom css file is used to improve the interface of this application. It can be found here.

[3]:
from IPython.display import HTML
display(HTML("<head><link rel='stylesheet' type='text/css' href='./../../static/custom.css'></head>"))
display(HTML("<style>.container { width:100% !important; }</style>"))
display(HTML("<style>.widget-label { display: contents !important; }</style>"))
display(HTML("<style>.slider-container { margin: 12px !important; }</style>"))
display(HTML("<style>.jupyter-widgets { overflow: auto !important; }</style>"))

Packages

[4]:
from bqplot import *
import bqplot as bq
import bqplot.marks as bqm
import bqplot.scales as bqs
import bqplot.axes as bqa

import ipywidgets as widgets

import urllib.parse
import webbrowser

import sys

Physical functions

This are the functions that have a physical meaning:

  • get_relative_isotherms
  • get_derivative_y_by_x
[5]:
def get_relative_isotherms(v_range, T_range):
    """This function calculates the theoretical p(v, T) plane
        (in reduced coordinates) according to van der Waals
        equation of state from a given range of volumes
        and tenperatures.

    Args:
        v_range: An array containing the values of v
        (in reduced coordinates)for which the isotherms must be
        calculated.\n
        T_range: An array containing the values of T
        (in reduced coordinates)for which the isotherms must be
        calculated.\n


    Returns:
        isotherms: A list consisted of numpy arrays containing the
        pressures of each isotherm.
    """

    isotherms = []

    for T in T_range:
        p_R = []
        for v in v_range:
            val = (8.0/3.0*T/(v - 1.0/3.0) - 3.0/v**2)
            p_R = np.append(p_R, val)

        isotherms.append(p_R)

    return isotherms
[6]:
def get_derivative_y_by_x(y_values, x_values):
    """This function calculates the derivative an y array
    with respect to an x array calculated with the difference quotient.

    Args:
        y_values: An array containing the values of y.\n
        x_values: An array containing the values of x.\n


    Returns:
        der: An array containing the values of the
        derivative of y_values with respect to x_values.
    """

    der = []


    for i in range(len(x_values)):

        x = x_values[i]
        y = y_values[i]

        d = []

        l = np.size(y)

        for j in range(1, l):
            d.append((y[j] - y[j-1])/(x[j] - x[j-1]))

        der.append(d)

    return der

Main interface

[ ]:
v_values = np.linspace(0.4, 5.0, 500)
T_values = [0.85, 0.9, 0.95, 1.0, 1.05, 1.1, 1.15, 1.2]

p_values = get_relative_isotherms(v_values, T_values)

################################
######TOP BLOCK#################
################################

top_block = widgets.VBox(
    [],
    layout=widgets.Layout(align_items='center')
)

T_slider = widgets.SelectionSlider(
    options= T_values,
    value=T_values[0],
    description=r'\( T \)',
    disabled=False,
    continuous_update=True,
    orientation='horizontal',
    readout=True,
    layout = widgets.Layout(
        width = '300px',
        height = 'auto',
        margin='0 0 0 50px'
    )
)

T_slider.observe(change_tenperature, 'value')

change_view_button = widgets.ToggleButton(
    value=False,
    description='Presentation mode (OFF)',
    disabled=False,
    button_style='',
    tooltip='',
    icon='desktop',
    layout=widgets.Layout(
        width='initial',
        align_self='center'
    )
)

change_view_button.observe(change_view, 'value')

top_block.children = [
    change_view_button,
    T_slider
]

################################
######BOTTOM BLOCK##############
################################

bottom_block = widgets.HBox(
    [],
    layout=widgets.Layout(
        width='100%',
        align_items='center'
    )
)

dense_v_values = np.linspace(min(v_values), max(v_values), 10000)
dense_p_values = get_relative_isotherms(dense_v_values, T_values)

dense_v_values_filtered = []
dense_p_values_filtered = []

dense_v_values_inverted = []
dense_p_values_inverted = []

dense_v_values_rounded = []

for i in range(len(T_values)):

    i_in_range, = np.where(dense_p_values[i] < 2.0)

    dense_v_values_filtered.append(np.take(dense_v_values, i_in_range))
    dense_p_values_filtered.append(np.take(dense_p_values[i], i_in_range))
    dense_v_values_rounded.append(np.round(dense_v_values_filtered[i], 3))

v_text = widgets.HTML(
    value="<p>" + str(dense_v_values_rounded[T_slider.index][i]) + "</p>",
    layout = widgets.Layout(
        height='auto',
        margin='8px 0 0 10px',
        width='initial'
    )
)

der = get_derivative_y_by_x(dense_p_values_filtered, dense_v_values_filtered)

v_slider = widgets.IntSlider(
    min=0,
    max=len(der[T_slider.index])-1,
    value=0,
    description=r'\( v \)',
    disabled=False,
    continuous_update=True,
    orientation='horizontal',
    readout=False,
    layout = widgets.Layout(width = '75%', height='auto', margin='0 0 0 100px')
)

v_slider.observe(update_tracer, 'value')
v_slider.observe(update_text, 'value')

play = widgets.Play(
    interval=1,
    value=0,
    min=0,
    max=v_slider.max,
    step=1,
    description="Press play",
    disabled=False
)

widgets.jslink((play, 'value'), (v_slider, 'value'));

scale_x = bqs.LinearScale(min = min(v_values), max = max(v_values))
scale_y = bqs.LinearScale(min = 0.0, max = 2.0)

color_scale = bqs.ColorScale(
    colors = ['#FF0000', '#FFfa00'],
    min=min(T_values),
    max=max(T_values)
)

axis_x_001 = bqa.Axis(
    scale=scale_x,
    tick_format='.2f',
    tick_style={'font-size': '15px'},
    tick_values = [0.5, 2.5, 5.0],
    grid_lines = 'none',
    grid_color = '#8e8e8e',
    label='v',
    label_location='middle',
    label_style={'stroke': 'black', 'default-size': 35},
    label_offset='50px'
)

axis_y_001 = bqa.Axis(
    scale=scale_y,
    tick_format='.1f',
    tick_style={'font-size': '15px'},
    tick_values = [0.0, 1, 2],
    grid_lines = 'none',
    grid_color = '#8e8e8e',
    orientation='vertical',
    label='p',
    label_location='middle',
    label_style={'stroke': 'red', 'default_size': 35},
    label_offset='50px'
)

axis_color = bqa.ColorAxis(
    label = 'T',
    scale=color_scale,
    tick_format='.2f',
    orientation='vertical',
    side='right'
)

fig_11A_001 = Figure(
    title='p vs v (fixed T, reduced variables)',
    marks=[],
    axes=[axis_x_001, axis_y_001, axis_color],
    animation_duration=0,
    legend_location='top-right',
    background_style= {'fill': 'white',  'stroke': 'black'},
    fig_margin=dict(top=70, bottom=75, left=80, right=100),
    toolbar = True,
    layout = widgets.Layout(width='85%')
)

lines_11A_001 = bqm.Lines(
    x = v_values,
    y = p_values,
    scales = {'x': scale_x, 'y': scale_y, 'color': color_scale},
    opacities = [1.0] + [0.0 for i in range(len(T_values)-1)],
    visible = True,
    color = T_values,
)

tracer_11A_001 = bqm.Scatter(
    name = '',
    x = [0.0],
    y = [0.0],
    scales = {'x': scale_x, 'y': scale_y},
    opacity = [1.0],
    visible = False,
    colors = ['#2807a3'],
)

fig_11A_001.marks = [
    lines_11A_001,
    tracer_11A_001
]

scale_x_002 = bqs.LinearScale(min = 0.0, max = 2.0)
scale_y_002 = bqs.LinearScale(min = -2.0, max = 2.0)

axis_y_002 = bqa.Axis(
    scale=scale_y_002,
    tick_format='.2f',#'0.2f',
    tick_style={'font-size': '15px'},
    tick_values = [-2, -1, 0, 1, 2],
    grid_lines = 'none',
    grid_color = '#8e8e8e',
    orientation='vertical',
    label='dp/dv',
    label_location='middle',
    label_style={'stroke': 'red', 'default_size': 35},
    label_offset='50px'
)

fig_11A_002 = Figure(
    title='dp/dv vs v (fixed T, reduced variables)',
    marks=[],
    axes=[axis_x_001, axis_y_002],
    animation_duration=0,
    legend_location='top-right',
    background_style= {'fill': 'white',  'stroke': 'black'},
    fig_margin=dict(top=70, bottom=75, left=80, right=100),
    toolbar = True,
    layout = widgets.Layout(width='85%')
)

lines_11A_002 = bqm.Lines(
    x = [0.4],
    y = [1.0],
    scales = {'x': scale_x, 'y': scale_y_002, 'color':color_scale},
    opacities = [1.0],
    visible = True,
    color = T_values,
)

zero_11A_002 = bqm.Lines(
    x = v_values,
    y = [0.0 for v in v_values],
    scales = {'x': scale_x, 'y': scale_y_002},
    opacities = [1.0],
    visible = True,
    colors = ['#FF0000'],
    line_style = 'dotted'
)

fig_11A_002.marks = [
    zero_11A_002,
    lines_11A_002
]

restart_button = widgets.Button(
    description='Clean figure',
    disabled=False,
    button_style='',
    tooltip="",
    layout = widgets.Layout(height='auto')
)

restart_button.on_click(restart_derivative)

show_all_button = widgets.Button(
    description='Show all derivatives',
    disabled=False,
    button_style='',
    tooltip="",
    layout = widgets.Layout(height='auto', width='initial')
)

show_all_button.on_click(show_all_derivatives)

prepare_export_fig_11A_001_button = widgets.Button(
    description='Export',
    disabled=False,
    button_style='',
    tooltip='',
)

prepare_export_fig_11A_001_button.on_click(prepare_export)

prepare_export_fig_11A_002_button = widgets.Button(
    description='Export',
    disabled=False,
    button_style='',
    tooltip='',
)

prepare_export_fig_11A_002_button.on_click(prepare_export)

slider_box = widgets.HBox([
    v_slider, v_text
    ],
    layout=widgets.Layout(
        height='auto',
        width='100%',
        align_items='center'
    )
)

bottom_block.children = [
    widgets.VBox([
        fig_11A_001,
        prepare_export_fig_11A_001_button,
        slider_box,
        play
    ],
    layout=widgets.Layout(
        height='auto',
        width='50%',
        align_items='center'
    )
    ),
    widgets.VBox([
        fig_11A_002,
        prepare_export_fig_11A_002_button,
        restart_button,
        show_all_button
    ],
    layout=widgets.Layout(
        height='auto',
        width='50%',
        align_items='center'
    )
    )
]

main_block_11A_000 = widgets.VBox(
    [],
    layout=widgets.Layout(align_items='center')
)

main_block_11A_000.children = [
    top_block,
    bottom_block
]

figures = [
    fig_11A_001,
    fig_11A_002
]

main_block_11A_000

Visualization of molar volume during a liquid-gas phase transition

Code: #112-000

File: apps/van_der_waals/phase_transition_volume.ipynb

Run it online: Binder


The aim of this notebook is to visualize the change in molar volume during a first-orden liquid-gas transition.

Interface

The main interface (main_block_112_000) is divided in two HBox: top_block_112_000 and bottom_block_112_000. bottom_block_112_000 contains of 3 bqplot Figures: fig_112_001, fig_112_002 and fig_112_003.

[1]:
from IPython.display import Image
Image(filename='../../static/images/apps/112-000_1.png')
[1]:
_images/apps_van_der_waals_phase_transition_volume_3_0.png

The slider T_slider updates the values of \(T\) which updates the bars and scatter points of fig_112_001, fig_112_002 and fig_112_003.

[2]:
Image(filename='../../static/images/apps/112-000_2.png')
[2]:
_images/apps_van_der_waals_phase_transition_volume_5_0.png

CSS

A custom css file is used to improve the interface of this application. It can be found here.

[3]:
from IPython.display import HTML
display(HTML("<head><link rel='stylesheet' type='text/css' href='./../../static/custom.css'></head>"))
display(HTML("<style>.container { width:100% !important; }</style>"))

Packages

[4]:
from bqplot import *
import bqplot as bq
import bqplot.marks as bqm
import bqplot.scales as bqs
import bqplot.axes as bqa

import ipywidgets as widgets

import numpy as np

import urllib.parse
import webbrowser

import sys

Physical functions

This are the functions that have a physical meaning:

  • get_relative_isotherms
  • experimetal_isotherms
  • get_roots
  • p_indefinite_integral
  • p_definite_integral
  • find_real_fixed_T
  • get_volumes_propotions
[5]:
def get_relative_isotherms(v_range, T_range):
    """This function calculates the theoretical p(v, T) plane
        (in reduced coordinates) according to Van der Waals
        equation of state from a given range of volumes
        and tenperatures.

    Args:
        v_range: An array containing the values of v
        (in reduced coordinates)for which the isotherms must be
        calculated.\n
        T_range: An array containing the values of v
        (in reduced coordinates)for which the isotherms must be
        calculated.\n


    Returns:
        isotherms: A list consisted consisted of numpy arrays containing the
        pressures of each isotherm.
    """

    isotherms = []

    for T in T_range:
        p_R = []
        for v in v_range:
            val = (8.0/3.0*T/(v - 1.0/3.0) - 3.0/v**2)
            p_R = np.append(p_R, val)

        isotherms.append(p_R)

    return isotherms
[6]:
def experimental_isotherms(p_range, v_range, T_range, fixed_p, fixed_T):
    """This function calculates the experimental p(v, T) plane
        (in reduced coordinates) according to van der Waals
        equation of state for a given range of volumes
        and tenperatures or for a given range of volumes
        and pressures.

    Args:
        p_range: An array containing the values of p
        (in reduced coordinates)for which the isotherms must be
        calculated. Only used if fixed_p == True.\n
        v_range: An array containing the values of v
        (in reduced coordinates)for which the isotherms must be
        calculated.\n
        T_range: An array containing the values of v
        (in reduced coordinates)for which the isotherms must be
        calculated. Only used if fixed_T == True.\n
        fixed_p: Boolean variable which represents if the isotherms
        must be calculated for a given pressures.\n
        fixed_T: Boolean variable which represents if the isotherms
        must be calculated for a given pressures.\n


    Returns:
        expe_data: A list consisted of numpy arrays containing the
        pressures of each theoretical isotherm.\n
        theo_data: A list consisted of numpy arrays containing the
        pressures of each theoretical isotherm.\n
        v_limits: A list consisted of arrays of the volume limits of
        the phase-transition of each subcritical isotherm.\n
        p_limits: A list consisted of arrays of the pressure limits of
        the phase-transition of each subcritical isotherm.\n
        tenperatures: A list consisted of the tenperatures of the
        isotherms.\n
    """

    if fixed_T:

        theo_data = get_relative_isotherms(v_range, T_range)
        expe_data = []

        v_limits = []
        p_limits = []

        p_range = np.linspace(0.001, 1.0, num=10000)
        pressures, v_isobaric_limits = find_real_fixed_T(p_range, T_range)

        for i in range(len(theo_data)):

            p_expe = []

            if i < len(v_isobaric_limits):

                    v_lim = v_isobaric_limits[i]

                    if len(v_lim) > 1: #check if there is only one point
                        for j in range(len(v_range)):

                            if v_range[j] > v_lim[0] and v_range[j] < v_lim[1]:
                                p_expe.append(pressures[i])

                            else:
                                p_expe.append(theo_data[i][j])

                        v_limits = np.append(v_limits, [v_lim[0], v_lim[1]])
                        p_limits = np.append(p_limits, [pressures[i], pressures[i]])

                    else:
                        p_expe = theo_data[i]
                        v_limits = np.append(v_limits, [1.0])
                        p_limits = np.append(p_limits, [1.0])

            else:

                p_expe = theo_data[i]

            expe_data.append(p_expe)

        tenperatures = T_range

        return expe_data, theo_data, p_limits, v_limits, tenperatures

    elif fixed_p:

        tenperatures, v_isobaric_limits = find_real_fixed_p(p_range, T_range)

        theo_data = get_relative_isotherms(v_range, tenperatures)
        expe_data = []

        v_limits = []
        p_limits = []

        for i in range(len(theo_data)):

            p_expe = []

            if i < len(v_isobaric_limits):

                    v_lim = v_isobaric_limits[i]

                    if len(v_lim) > 1: #check if there is only one point

                        for j in range(len(v_range)):

                            if v_range[j] > v_lim[0] and v_range[j] < v_lim[1]:
                                p_expe.append(p_range[i])

                            else:
                                p_expe.append(theo_data[i][j])

                        v_limits = np.append(v_limits, [v_lim[0], v_lim[1]])
                        p_limits = np.append(p_limits, [p_range[i], p_range[i]])

                    else:
                        p_expe = theo_data[i]
                        v_limits = np.append(v_limits, [1.0])
                        p_limits = np.append(p_limits, [1.0])

            else:

                p_expe = theo_data[i]

            expe_data.append(p_expe)

        return expe_data, theo_data, p_limits, v_limits, tenperatures
[7]:
def get_roots(p, T):
    """This function finds the intersection between an isobaric curve
       and Van der Waals equation of state for a given T.\n
       Values of v with no physical meaning are dismissed
       (v < 0 or complex).

    Args:
        p: Pressure of the isobaric curve.\n
        T: Tenperature of the isotherm.\n


    Returns:
        roots_in_range: A sorted list of the volumes in which the
        isobaric curve intersects the isotherm.\n
    """

    roots = np.roots([1.0, - 1.0/3.0*(1.0 + 8.0*T/p), 3.0/p, -1.0/p])
    roots_in_range = []

    for root in roots:

        # A third degree polynomial has 3 complex roots,
        # but we are only interested in the ones which are
        # purely real.

        if np.isreal(root):

            root = np.real(root)

            if root > 0:

                roots_in_range.append(root)

    roots_in_range.sort()

    return roots_in_range
[8]:
def p_indefinite_integral(p_0, v_0, T):
    """This function calculates the indefinite integral between
    a van der Waals isotherm and a isobaric line.

    Args:
        p0: Isobaric line's pressure.\n
        v0: Value of the volume.\n
        T: Value of the tenperature.\n

    Returns:
        integral: Value of the indefinite integral between a
        van der Waals isotherm at T and a isobaric line of p0 at a
        volume v0.\n
    """

    integral = 8.0/3.0 * T *np.log(v_0 - 1.0/3.0) + 3.0/v_0 - p_0*v_0

    return integral
[9]:
def definite_integral(p_0, v_range, T):
    """This function 'p_indefinite_integral' function to calculate
    the definite integral between a van der Waals isotherm and a
    isobaric line.

    Args:
        p0: Isobaric line's pressure.\n
        v_range: Tuple or list consisted of volume limits.\n
        T: Value of the tenperature.\n

    Returns:
        integral: Value of the definite integral between a
        van der Waals isotherm at T and a isobaric line of p0 in a
        volume range v_range.\n
    """

    v_0, v_1 = v_range[0], v_range[1]

    integral = p_indefinite_integral(p_0, v_1, T) - p_indefinite_integral(p_0, v_0, T)

    return integral
[10]:
def find_real_fixed_T(p_values, T_values):
    """This function uses Maxwell's construction to find the
       pressures in which phase transition happens given some
       fixed tenperatures.\n

    Args:
        p_values: List of pressures in which the real isotherm is
        searched.\n
        T_values: List of tenperatures of the isotherms.\n


    Returns:
        pressures: List of pressures in which phase transition
        happens.\n
        v_range: Volume limits of phase transition zones.
    """

    eps = 1e-3

    pressures = []
    v_ranges = []

    for T in T_values:

        if T < 1.0:

            for p in p_values:

                roots = get_roots(p, T)

                if len(roots) == 3:

                    v_range = [roots[0], roots[2]]
                    area = definite_integral(p, v_range, T)

                    if abs(area) < eps:

                        pressures.append(p)
                        v_ranges.append(v_range)

                        break

        elif T == 1.0:

            pressures.append(1.0)
            v_ranges.append([1.0])

    return pressures, v_ranges
[11]:
def get_volumes_propotions(v_limits, v):
    """This function calculates the propotion of gas/liquid
       during a phase transistion at a volume v.\n

    Args:
        v_limits: Volume limits in which the phase transition
        happens.\n
        v: value of the volume.\n


    Returns:
        x_g: propotion of the gas phase.\n
        x_l: propotion of the liquid phase.
    """

    v_l = v_limits[0]
    v_g = v_limits[1]

    x_l = (v_g - v)/(v_g - v_l)
    x_g = (v - v_l)/(v_g - v_l)

    return x_g, x_l

Main interface

[ ]:
"""This module visualizes the change in molar volumen of both phases
during a liquid-gas phase transition.

"""

v_values = np.linspace(0.4, 5.0, 500)
T_values = np.round(np.linspace(0.85, 1.2, 10), 2)

colors = ['#221ba1','#b5e5ff'] #light blue, dark_blue
gradient = generate_gradient(colors[0], colors[1], 500)

expe_data, theo_data, p_limits, v_limits, tenperatures = experimental_isotherms(
    [],
    v_values,
    T_values,
    fixed_p = False,
    fixed_T = True,
)

#######################
#######TOP BLOCK####
#######################

top_block = widgets.VBox([], layout=widgets.Layout(align_items='center', width='100%'))

change_view_button = widgets.ToggleButton(
    value=False,
    description='Presentation mode (OFF)',
    disabled=False,
    button_style='',
    tooltip='',
    icon='desktop',
    layout=widgets.Layout(
        width='auto'
    )
)

change_view_button.observe(change_view, 'value')

T_slider = widgets.SelectionSlider(
    options= T_values,
    value=T_values[0],
    description=r'\( T \)',
    disabled=False,
    continuous_update=True,
    orientation='horizontal',
    readout=True,
    layout = widgets.Layout(
        width = '33%',
        align_self='center',
        margin='20px 0 0 0'
    )
)

T_slider.observe(change_tenperature, 'value')

top_block.children = [
    change_view_button,
    T_slider
]

#######################
#######MIDDLE BLOCK####
#######################

middle_block = widgets.HBox([], layout=widgets.Layout(align_items='center', width='100%'))

scale_x = bqs.LinearScale(min = 0.0, max = max(v_values))
scale_y = bqs.LinearScale(min = 0, max = 2.0)

axis_x = bqa.Axis(
    scale=scale_x,
    tick_format='.2f',
    tick_style={'font-size': '15px'},
    tick_values = np.linspace(0, max(v_values), 5),
    grid_lines = 'none',
    grid_color = '#8e8e8e',
    label='v',
    label_location='middle',
    label_style={'stroke': 'black', 'default-size': 35},
    label_offset='50px'
)

axis_y = bqa.Axis(
    scale=scale_y,
    tick_format='.1f',
    tick_style={'font-size': '15px'},
    tick_values = np.linspace(0, 2.0, 4),
    grid_lines = 'none',
    grid_color = '#8e8e8e',
    orientation='vertical',
    label='p',
    label_location='middle',
    label_style={'stroke': 'red', 'default_size': 35},
    label_offset='50px'
)

fig_112_001 = bq.Figure(
    title='p vs v (fixed T, reduced variables)',
    marks=[],
    axes=[axis_x, axis_y],
    animation_duration=0, #500,
    legend_location='top-right',
    legend_text =  {'font-size': '14px'},
    background_style= {'fill': 'white',  'stroke': 'black'},
    fig_margin=dict(top=80, bottom=60, left=80, right=30),
    toolbar = True,
    layout = widgets.Layout(width='100%', height='500px'),
)

lines_112_001 = bqm.Lines(
    x = v_values,
    y = [theo_data[T_slider.index], expe_data[T_slider.index]],
    scales = {'x': scale_x, 'y': scale_y},
    opacities = [0.2, 1.0],
    visible = True,
    colors = ['red'],
    labels = ['theoretical', 'experimental'],
    display_legend = True
)

tracer_112_001 = bqm.Scatter(
    name = '',
    x = [0.0],
    y = [0.0],
    scales = {'x': scale_x, 'y': scale_y},
    opacity = [1.0, 0.0],
    visible = False,
    colors = ['#2807a3'],
)

fig_112_001.marks = [lines_112_001, tracer_112_001]

v_values_rounded = np.round(v_values, 3)

v_slider = widgets.SelectionSlider(
    options=v_values_rounded,
    value=v_values_rounded[-1],
    description=r'\( v \)',
    disabled=False,
    continuous_update=True,
    orientation='horizontal',
    readout=True,
    layout = widgets.Layout(width = '100%', margin='0 50px 0 50px')
)

v_slider.observe(update_tracer, 'value')


new_x_scale = bqs.LinearScale(min=0.5, max=1.5)

bar_112_002 = bqm.Bars(
    x=[1.0],
    y=[max(v_values)],
    scales={'x': bqs.OrdinalScale(), 'y': scale_x},
    colors=['#b5e5ff', '#221ba1'],
)

original_112_002 = bqm.Bars(
    x=[1.0],
    y=[max(v_values)],
    scales={'x': bqs.OrdinalScale(), 'y': scale_x},
    colors=['#d9d9d9'],
    opacities = [0.2]
)

axis_x_002 = bqa.Axis(
    scale=new_x_scale,
    tick_format='.2f',
    tick_style={'font-size': '15px'},
    num_ticks=0,
    grid_lines = 'none',
    grid_color = '#8e8e8e',
    label='',
    label_location='middle',
    label_style={'stroke': 'black', 'default-size': 35},
    label_offset='50px'
)

axis_y_002 = bqa.Axis(
    scale=scale_x,
    tick_format='.1f',
    tick_style={'font-size': '15px'},
    tick_values = np.linspace(0, max(v_values), 4),
    grid_lines = 'none',
    grid_color = '#8e8e8e',
    orientation='vertical',
    label='v',
    label_location='middle',
    label_style={'stroke': 'red', 'default_size': 35},
    label_offset='50px'
)

fig_112_002 = bq.Figure(
    title='',
    marks=[],
    axes=[axis_x_002, axis_y_002],
    animation_duration=0,
    legend_location='top-right',
    background_style= {'fill': 'white',  'stroke': 'black'},
    fig_margin=dict(top=65, bottom=75, left=80, right=30),
    toolbar = True,
    layout = widgets.Layout(width='100%', height='500px')
)

fig_112_002.marks = [original_112_002, bar_112_002, ]

bar_112_003 = bqm.Bars(
    x=[[1.0],[1.0]],
    y=[[0.0], [max(v_values)]],
    scales={'x': bqs.OrdinalScale(), 'y': scale_x},
    colors=['#221ba1', '#b5e5ff'],
    type='grouped'
)

lines_112_003 = bqm.Lines(
    x = [0.0, 5.0],
    y = [max(v_values), max(v_values)],
    scales = {
        'x': bqs.LinearScale(min=0, max=1.0),
        'y': scale_x
    },
    visible = True,
    colors = ['#eb9c00'],
)

axis_x_003 = bqa.Axis(
    scale=new_x_scale,
    tick_format='.2f',
    tick_style={'font-size': '15px'},
    num_ticks=0,
    grid_lines = 'none',
    grid_color = '#8e8e8e',
    label='',
    label_location='middle',
    label_style={'stroke': 'black', 'default-size': 35},
    label_offset='50px'
)

axis_y_003 = bqa.Axis(
    scale=scale_x,
    tick_format='.1f',
    tick_style={'font-size': '15px'},
    tick_values = np.linspace(0, max(v_values), 4),
    grid_lines = 'none',
    grid_color = '#8e8e8e',
    orientation='vertical',
    label='v',
    label_location='middle',
    label_style={'stroke': 'red', 'default_size': 35},
    label_offset='50px'
)

fig_112_003 = bq.Figure(title='',
    marks=[],
    axes=[axis_x_003, axis_y_003],
    animation_duration=0,
    legend_location='top-right',
    background_style= {'fill': 'white',  'stroke': 'black'},
    fig_margin=dict(top=65, bottom=75, left=80, right=30),
    toolbar = True,
    layout = widgets.Layout(width='100%', height='500px')
)

fig_112_003.marks = [bar_112_003, lines_112_003]

prepare_export_fig_112_001_button = widgets.Button(
    description='Export',
    disabled=False,
    button_style='',
    tooltip='',
)

prepare_export_fig_112_001_button.on_click(prepare_export)

prepare_export_fig_112_002_button = widgets.Button(
    description='Export',
    disabled=False,
    button_style='',
    tooltip='',
)

prepare_export_fig_112_002_button.on_click(prepare_export)

prepare_export_fig_112_003_button = widgets.Button(
    description='Export',
    disabled=False,
    button_style='',
    tooltip='',
)

prepare_export_fig_112_003_button.on_click(prepare_export)

export_button = widgets.HTML(
    value = ""
)

middle_block.children = [
    widgets.VBox([
         fig_112_001,
         prepare_export_fig_112_001_button,
         v_slider
    ],
        layout = widgets.Layout(
            align_items='center',
            width='33%'
        )
    ),
    widgets.VBox([
        fig_112_002,
        prepare_export_fig_112_002_button,
    ],
        layout = widgets.Layout(
            align_items='center',
            width='33%'
        )
    ),
    widgets.VBox([
        fig_112_003,
        prepare_export_fig_112_003_button
    ],
        layout = widgets.Layout(
            align_items='center',
            width='33%'
        )
    ),
]

#######################
#######BOTTOM BLOCK######
#######################

bottom_block_112_000 = widgets.VBox([
    widgets.HBox([
        widgets.HTML(
            '<svg width="20" height="20">' \
            '<rect width="20" height="20"' \
            'style="fill:#221ba1;stroke-width:3;' \
            'stroke:rgb(0,0,0)"/></svg>' \
        ),
        widgets.Label("Liquid phase",),
        widgets.HTML('<span style="display:inline-block; width: 100px;"></span>'),
        widgets.HTML(
                '<svg width="20" height="20">' \
                '<rect width="20" height="20"' \
                'style="fill:#b5e5ff;stroke-width:3;' \
                'stroke:rgb(0,0,0)"/></svg>' \
            ),
        widgets.Label("Gaseous phase"),
    ])],
    layout=widgets.Layout(
        align_items='center',
        width='100%'
    )
)



#######################
#######MAIN BLOCK######
#######################

main_block_112_000 = widgets.VBox(
    [],
    layout=widgets.Layout(
        align_items='center',
        width='100%'
    )
)

main_block_112_000.children = [
    top_block,
    middle_block,
    bottom_block_112_000
]


figures = [
    fig_112_001,
    fig_112_002,
    fig_112_003
]

main_block_112_000

Change in molar entropy during a first-order phase transition

Code: #11D-000

File: apps/van_der_waals/entropy.ipynb

Run it online: Binder


The aim of this notebook is to visualize the change in molar entropy during a first-orden liquid-gas transition.

Interface

The main interface (main_block_11D_000) is divided in two HBox: top_block_11D_000 and bottom_block_11D_000. bottom_block_11D_000 contains of 2 bqplot Figures: fig_11D_001 and fig_11D_002.

[1]:
from IPython.display import Image
Image(filename='../../static/images/apps/11D-000_1.png')
[1]:
_images/apps_van_der_waals_entropy_3_0.png

The slider T_slider updates the values of \(T\) which updates the lines and scatter points of fig_11D_001 and fig_11D_002.

[2]:
Image(filename='../../static/images/11D-000_2.png')
[2]:
_images/apps_van_der_waals_entropy_5_0.png

CSS

A custom css file is used to improve the interface of this application. It can be found here.

[3]:
from IPython.display import HTML
display(HTML("<head><link rel='stylesheet' type='text/css' href='./../../static/custom.css'></head>"))
display(HTML("<style>.container { width:100% !important; } .jupyter-button {white-space: normal !important;}</style>"))

Packages

[4]:
from bqplot import *
import bqplot as bq
import bqplot.marks as bqm
import bqplot.scales as bqs
import bqplot.axes as bqa

import ipywidgets as widgets

from scipy.signal import argrelextrema

import urllib.parse
import webbrowser

import sys

Physical functions

This are the functions that have a physical meaning:

  • get_relative_isotherms
  • experimetal_isotherms
  • get_roots
  • p_indefinite_integral
  • p_definite_integral
  • find_real_fixed_p
  • find_real_fixed_T
[5]:
def get_relative_isotherms(v_range, T_range):
    """This function calculates the theoretical p(v, T) plane
        (in reduced coordinates) according to van der Waals
        equation of state from a given range of volumes
        and tenperatures.

    Args:
        v_range: An array containing the values of v
        (in reduced coordinates)for which the isotherms must be
        calculated.\n
        T_range: An array containing the values of T
        (in reduced coordinates)for which the isotherms must be
        calculated.\n


    Returns:
        isotherms: A list consisted of numpy arrays containing the
        pressures of each isotherm.
    """

    isotherms = []

    for T in T_range:
        p_R = []
        for v in v_range:
            val = (8.0/3.0*T/(v - 1.0/3.0) - 3.0/v**2)
            p_R = np.append(p_R, val)

        isotherms.append(p_R)

    return isotherms
[6]:
def experimental_isotherms(p_range, v_range, T_range, fixed_p, fixed_T):
    """This function calculates the experimental p(v, T) plane
        (in reduced coordinates) according to van der Waals
        equation of state for a given range of volumes
        and tenperatures or for a given range of volumes
        and pressures.

    Args:
        p_range: An array containing the values of p
        (in reduced coordinates)for which the isotherms must be
        calculated. Only used if fixed_p == True.\n
        v_range: An array containing the values of v
        (in reduced coordinates)for which the isotherms must be
        calculated.\n
        T_range: An array containing the values of v
        (in reduced coordinates)for which the isotherms must be
        calculated. Only used if fixed_T == True.\n
        fixed_p: Boolean variable which represents if the isotherms
        must be calculated for a given pressures.\n
        fixed_T: Boolean variable which represents if the isotherms
        must be calculated for a given pressures.\n


    Returns:
        expe_data: A list consisted of numpy arrays containing the
        pressures of each theoretical isotherm.\n
        theo_data: A list consisted of numpy arrays containing the
        pressures of each theoretical isotherm.\n
        v_limits: A list consisted of arrays of the volume limits of
        the phase-transition of each subcritical isotherm.\n
        p_limits: A list consisted of arrays of the pressure limits of
        the phase-transition of each subcritical isotherm.\n
        tenperatures: A list consisted of the tenperatures of the
        isotherms.\n
    """

    if fixed_T:

        theo_data = get_relative_isotherms(v_range, T_range)
        expe_data = []

        v_limits = []
        p_limits = []

        p_range = np.linspace(0.001, 1.0, num=10000)
        pressures, v_isobaric_limits = find_real_fixed_T(p_range, T_range)

        for i in range(len(theo_data)):

            p_expe = []

            if i < len(v_isobaric_limits):

                    v_lim = v_isobaric_limits[i]

                    if len(v_lim) > 1: #check if there is only one point
                        for j in range(len(v_range)):

                            if v_range[j] > v_lim[0] and v_range[j] < v_lim[1]:
                                p_expe.append(pressures[i])

                            else:
                                p_expe.append(theo_data[i][j])

                        v_limits = np.append(v_limits, [v_lim[0], v_lim[1]])
                        p_limits = np.append(p_limits, [pressures[i], pressures[i]])

                    else:
                        p_expe = theo_data[i]
                        v_limits = np.append(v_limits, [1.0])
                        p_limits = np.append(p_limits, [1.0])

            else:

                p_expe = theo_data[i]

            expe_data.append(p_expe)

        tenperatures = T_range

        return expe_data, theo_data, p_limits, v_limits, tenperatures

    elif fixed_p:

        tenperatures, v_isobaric_limits = find_real_fixed_p(p_range, T_range)

        theo_data = get_relative_isotherms(v_range, tenperatures)
        expe_data = []

        v_limits = []
        p_limits = []

        for i in range(len(theo_data)):

            p_expe = []

            if i < len(v_isobaric_limits):

                    v_lim = v_isobaric_limits[i]

                    if len(v_lim) > 1: #check if there is only one point

                        for j in range(len(v_range)):

                            if v_range[j] > v_lim[0] and v_range[j] < v_lim[1]:
                                p_expe.append(p_range[i])

                            else:
                                p_expe.append(theo_data[i][j])

                        v_limits = np.append(
                            v_limits,
                            [v_lim[0],
                             v_lim[1]]
                        )
                        p_limits = np.append(
                            p_limits,
                            [p_range[i],
                             p_range[i]]
                        )

                    else:
                        p_expe = theo_data[i]
                        v_limits = np.append(v_limits, [1.0])
                        p_limits = np.append(p_limits, [1.0])

            else:

                p_expe = theo_data[i]

            expe_data.append(p_expe)

        return expe_data, theo_data, p_limits, v_limits, tenperatures
[7]:
def get_roots(p, T):
    """This function calculates the roots of a van der Waals
    isotherm of a given T and set of pressures.

    Args:
        p: Numpy array consisted of the pressures of the isotherm.\n
        T: Value of the tenperature.\n

    Returns:
        roots_in_range: A list consisted of the real roots.\n
    """


    roots = np.roots([1.0, - 1.0/3.0*(1.0 + 8.0*T/p), 3.0/p, -1.0/p])
    roots_in_range = []

    for root in roots:
        if np.isreal(root):
            root = np.real(root)
            if root > 0:
                roots_in_range.append(root)
    roots_in_range.sort()

    return roots_in_range
[8]:
def p_indefinite_integral(p_0, v_0, T):
    """This function calculates the indefinite integral between
    a van der Waals isotherm and a isobaric line.

    Args:
        p0: Isobaric line's pressure.\n
        v0: Value of the volume.\n
        T: Value of the tenperature.\n

    Returns:
        integral: Value of the indefinite integral between a
        van der Waals isotherm at T and a isobaric line of p0 at a
        volume v0.\n
    """

    integral = 8.0/3.0 * T *np.log(v_0 - 1.0/3.0) + 3.0/v_0 - p_0*v_0

    return integral
[9]:
def definite_integral(p_0, v_range, T):
    """This function 'p_indefinite_integral' function to calculate
    the definite integral between a van der Waals isotherm and a
    isobaric line.

    Args:
        p0: Isobaric line's pressure.\n
        v_range: Tuple or list consisted of volume limits.\n
        T: Value of the tenperature.\n

    Returns:
        integral: Value of the definite integral between a
        van der Waals isotherm at T and a isobaric line of p0 in a
        volume range v_range.\n
    """

    v_0, v_1 = v_range[0], v_range[1]

    integral = p_indefinite_integral(p_0, v_1, T) - p_indefinite_integral(p_0, v_0, T)

    return integral
[10]:
def find_real_fixed_T(p_values, T_values):
    """This function uses Maxwell's construction to find the
       pressures in which phase transition happens given some
       fixed tenperatures.\n

    Args:
        p_values: List of pressures in which the real isotherm is
        searched.\n
        T_values: List of tenperatures of the isotherms.\n


    Returns:
        pressures: List of pressures in which phase transition
        happens.\n
        v_range: Volume limits of phase transition zones.
    """

    eps = 1e-3

    pressures = []
    v_ranges = []

    for T in T_values:

        if T < 1.0:

            for p in p_values:

                roots = get_roots(p, T)

                if len(roots) == 3:

                    v_range = [roots[0], roots[2]]
                    area = definite_integral(p, v_range, T)

                    if abs(area) < eps:

                        pressures.append(p)
                        v_ranges.append(v_range)

                        break

        elif T == 1.0:

            pressures.append(1.0)
            v_ranges.append([1.0])

    return pressures, v_ranges
[11]:
def find_real_fixed_p(p_values, T_values):
    """This function uses Maxwell's construction to find the
       tenperatures in which phase transition happens given some
       fixed pressures.\n

    Args:
        p_values: List of pressures of the isotherms.\n
        T_values: List of tenperatures in which the real isotherm is
        searched.\n


    Returns:
        tenperatures: List of tenperatures in which phase transition
        happens.\n
        v_range: Volume limits of phase transition zones.
    """

    eps = 1e-3

    tenperatures = []
    v_ranges = []

    for p in p_values:

        if p < 1.0:

            for T in T_values:

                roots = get_roots(p, T)

                if len(roots) == 3:

                    v_range = [roots[0], roots[2]]
                    area = definite_integral(p, v_range, T)

                    if abs(area) < eps:

                        tenperatures.append(T)
                        v_ranges.append(v_range)

                        break

        elif p == 1.0:

            tenperatures.append(1.0)
            v_ranges.append([1.0])

    return tenperatures, v_ranges
[12]:
def get_entropy(v_values):
    """This function calculates the entropy for a given
    array of molar volumes.

    Args:
        v_values: Array consisted of the values of molar volume.

    Returns:
        s: Array containing the values of the entropy.
    """
    v_values = np.asarray(v_values)
    s = 8.0/3.0*np.log(3.0*v_values - 1.0)

    return s

Main interface

[ ]:
T_values = np.round(np.linspace(0.95, 0.97, 7), 3)
v_values = np.linspace(0.45, 5.2, 500)

colors = generate_gradient('#914040', '#feb901', len(T_values))

def_op = 0.2
opacities = [def_op for T in T_values]
opacities[0] = 1.0

data = experimental_isotherms(
    p_range=[],
    v_range=v_values,
    T_range=T_values,
    fixed_T = True,
    fixed_p = False
)

expe_p_values = data[0]
theo_p_values = data[1]

p_limits = data[2]
v_limits = data[3]
T_limits = data[4]

# The index of the pressure of the selected isotherm
selected = 3

p_0 = np.unique(p_limits)[selected]

state_v = []
T_values_repeated = []
colors_repeated = []

for i in range(len(T_values)):

    if i == selected:

        state_v.append(
            np.array([v_limits[2*i],v_limits[2*i+1]])
        )

        T_values_repeated.append(
            T_values[i]
        )

        colors_repeated.append(colors[i])

    else:

        idx = find_nearest_index(expe_p_values[i], p_0)
        state_v.append(v_values[idx])

    T_values_repeated.append(
        T_values[i]
    )

    colors_repeated.append(colors[i])

s_values = []

for v in state_v:
    s_values.append(get_entropy(v))

s_values_plain = np.hstack(s_values)

##########################
########TOP BLOCK#########
##########################

top_block_11D_000 = widgets.HBox(
    [],
    layout=widgets.Layout(
        align_items='center',
        width='100%'
    )
)

# fig_11D_001

scale_x_11D_001 = bqs.LinearScale(min = 0.55, max = 2.0)
scale_y_11D_001 = bqs.LinearScale(min = 0.75, max = 1.02)

axis_x_11D_001 = bqa.Axis(
    scale=scale_x_11D_001,
    tick_format='0.2f',
    tick_style={'font-size': '15px'},
    tick_values=[1.0, 2.0, 3.0, 4.0, 5.0],
    grid_lines = 'none',
    grid_color = '#8e8e8e',
    label='v',
    label_location='middle',
    label_style={'stroke': 'black', 'default-size': 35},
    label_offset='50px'
)

axis_y_11D_001 = bqa.Axis(
    scale=scale_y_11D_001,
    tick_format='0.2f',
    tick_style={'font-size': '15px'},
    tick_values=[0.0, 1.0],
    grid_lines = 'none',
    grid_color = '#8e8e8e',
    orientation='vertical',
    label='p',
    label_location='middle',
    label_style={'stroke': 'red', 'default_size': 35},
    label_offset='50px'
)

isotherms = bqm.Lines(
    x = np.array([v_values]),
    y = np.array([expe_p_values]),
    scales = {'x': scale_x_11D_001, 'y': scale_y_11D_001},
    opacities = opacities,
    visible = True,
    colors = colors,
    labels = [str(t) for t in T_limits],
)

isobaric = bqm.Lines(
    x = [v_values[0], v_values[-1]],
    y = np.array([p_0, p_0]),
    scales = {'x': scale_x_11D_001, 'y': scale_y_11D_001},
    opacities = [0.5],
    visible = True,
    colors = ['#01b6cf'],
    stroke_width = 5,
)

state = bqm.Scatter(
    x = [state_v[0]],
    y = [p_0],
    scales = {'x': scale_x_11D_001, 'y': scale_y_11D_001},
    visible = True,
    colors = ['black'],
    names = [],
    #tooltip = tt
)

fig_11D_001 = bq.Figure(
    title='van der Waals reduced isotherms',
    marks=[],
    axes=[axis_x_11D_001, axis_y_11D_001],
    animation_duration=0,
    layout = widgets.Layout(
        align_self='center',
    ),
    legend_location='top-right',
    background_style= {'fill': 'white','stroke': 'black'},
    fig_margin=dict(top=80,bottom=80,left=60,right=30),
    toolbar = True
)

fig_11D_001.marks = [
    isotherms,
    isobaric,
    state
]

# fig_11D_002

scale_x_11D_002 = bqs.LinearScale(min=0.945, max=0.975)
scale_y_11D_002 = bqs.LinearScale()

axis_x_11D_002 = bqa.Axis(
    scale=scale_x_11D_002,
    tick_format='0.2f',
    tick_style={'font-size': '15px'},
    tick_values=[0.95, 0.96, 0.97],
    grid_lines = 'none',
    grid_color = '#8e8e8e',
    label='T',
    label_location='middle',
    label_style={'stroke': 'black', 'default-size': 35},
    label_offset='50px'
)

axis_y_11D_002 = bqa.Axis(
    scale=scale_y_11D_002,
    tick_format='0.2f',
    tick_style={'font-size': '15px'},
    num_ticks=0,
    grid_lines = 'none',
    grid_color = '#8e8e8e',
    orientation='vertical',
    label='s',
    label_location='middle',
    label_style={'stroke': 'red', 'default_size': 35},
    label_offset='50px'
)

entropy_line = bqm.Lines(
    x = T_values_repeated,
    y = s_values_plain,
    scales = {'x': scale_x_11D_002, 'y': scale_y_11D_002},
    opacities = [0.5],
    visible = True,
    colors = ['#edba00'],
    labels = [str(t) for t in T_limits],
    stroke_width = 5,
)

entropy_scatter = bqm.Scatter(
    x = T_values_repeated,
    y = s_values_plain,
    scales = {'x': scale_x_11D_002, 'y': scale_y_11D_002},
    opacities = opacities,
    visible = True,
    colors = colors_repeated,
    labels = [str(t) for t in T_limits],
)

state_s = bqm.Scatter(
    x = [T_values[0]],
    y = [s_values_plain[0]],
    scales = {'x': scale_x_11D_002, 'y': scale_y_11D_002},
    visible = True,
    colors = ['black'],
    names = [],
)

fig_11D_002 = bq.Figure(
    title='Molar entropy vs tenperature',
    marks=[],
    axes=[axis_x_11D_002, axis_y_11D_002],
    animation_duration=0,
    layout = widgets.Layout(
        align_self='center',
    ),
    legend_location='top-right',
    background_style= {'fill': 'white','stroke': 'black'},
    fig_margin=dict(top=80,bottom=80,left=60,right=30),
    toolbar = True
)

fig_11D_002.marks = [
    entropy_line,
    entropy_scatter,
    state_s
]

prepare_export_fig_11D_001_button = widgets.Button(
    description='Export',
    disabled=False,
    button_style='',
    tooltip='',
    layout = widgets.Layout(
        align_self='center'
    )
)

prepare_export_fig_11D_001_button.on_click(prepare_export)

prepare_export_fig_11D_002_button = widgets.Button(
    description='Export',
    disabled=False,
    button_style='',
    tooltip='',
    layout = widgets.Layout(
        align_self='center'
    )
)

prepare_export_fig_11D_002_button.on_click(prepare_export)

top_block_11D_000.children = [
    widgets.VBox([
        fig_11D_001,
        prepare_export_fig_11D_001_button
    ]),
    widgets.VBox([
        fig_11D_002,
        prepare_export_fig_11D_002_button
    ])
]

bottom_block_11D_000 = widgets.VBox(
    [],
    layout=widgets.Layout(
        align_items='center',
        width='100%'
    )
)

isobaric_line =  widgets.HTML(
    value="<div style='width:30px;text-align:left;display:inline-block;" \
          + "border: 5px solid #01b6cf;opacity: 0.5'> </div>" \
          + "  Phase transition pressure"
)

T_slider = widgets.SelectionSlider(
    options=T_values,
    value=T_values[0],
    description=r'\( T_r \)',
    disabled=False,
    continuous_update=True,
    orientation='horizontal',
    readout=True,
    layout = widgets.Layout(
        width = '50%',
        margin = '45px 0 0 0'
    )
)

T_slider.observe(change_tenperature, 'value')

bottom_block_11D_000.children = [
    isobaric_line,
    T_slider
]

change_view_button = widgets.ToggleButton(
    value=False,
    description='Presentation mode (OFF)',
    disabled=False,
    button_style='',
    tooltip='',
    icon='desktop',
    layout=widgets.Layout(
        width='initial',
        align_self='center'
    )
)

change_view_button.observe(change_view, 'value')

main_block_11D_000 = widgets.VBox(
    [],
    layout=widgets.Layout(
        align_items='center',
        width='100%'
    )
)

main_block_11D_000.children = [
    change_view_button,
    top_block_11D_000,
    bottom_block_11D_000,
]

figures = [
    fig_11D_001,
    fig_11D_002
]

main_block_11D_000

Van der Waals isotherms in 3D

Code: #116-000

File: apps/van_der_waals/p_v_T_3D.ipynb

Run it online: Binder


Interface

The main interface (main_block_116_000) contains the 3D ipyvolume figure fig3d and the play and degrees_slider widgets. Those widgets update the angle of the camera of the figure.

[1]:
from IPython.display import Image
Image(filename='../../static/images/apps/116-000_1.png')
[1]:
_images/apps_van_der_waals_p_v_T_3D_3_0.png

Packages

[2]:
import ipywidgets as widgets
import ipyvolume as ipv

import numpy as np

Physical functions

This are the functions that have a physical meaning:

  • get_relative_isotherms
[3]:
def get_relative_isotherms(v_range, T_range):
    """This function calculates the theoretical p(v, T) plane
        (in reduced coordinates) according to van der Waals
        equation of state from a given range of volumes
        and tenperatures.

    Args:
        v_range: An array containing the values of v
        (in reduced coordinates)for which the isotherms must be
        calculated.\n
        T_range: An array containing the values of T
        (in reduced coordinates)for which the isotherms must be
        calculated.\n


    Returns:
        isotherms: A list consisted of numpy arrays containing the
        pressures of each isotherm.
    """

    isotherms = []

    for T in T_range:
        p_R = []
        for v in v_range:
            val = (8.0/3.0*T/(v - 1.0/3.0) - 3.0/v**2)
            p_R = np.append(p_R, val)

        isotherms.append(p_R)

    return isotherms

Main interface

[ ]:
T_values = np.linspace(0.8, 1.0, 10)
v_values = np.linspace(0.45, 5.0, 500)

p_values = get_relative_isotherms(v_values, T_values)

fig3d = ipv.pylab.figure(
    key=None,
    width=600,
    height=500,
    lighting=True,
    controls=True,
    controls_vr=False,
    controls_light=False,
    debug=False
)

ipv.pylab.xlim(min(v_values), max(v_values))
ipv.pylab.ylim(0.0, 2.0)
ipv.pylab.zlim(min(T_values), max(T_values))

ipv.pylab.xlabel('v')
ipv.pylab.ylabel('p')
ipv.pylab.zlabel('T')

ipv.pylab.view(azimuth=180, elevation=None, distance=None)

for i in range(len(T_values)):

    x_values = np.asarray(v_values)
    y_values = np.asarray(p_values[i])
    z_values = np.asarray(
        [T_values[i] for elem in v_values]
    )

    ipv.pylab.plot(
        x_values,
        y_values,
        z_values
    )

degrees_slider = widgets.IntSlider(
    value=180,
    min=0,
    max=360,
    step=1,
    description='',
    disabled=False,
    continuous_update=True,
    orientation='horizontal',
    readout=True,
    readout_format='d'
)

degrees_slider.observe(update_camera_angle, 'value')

play = widgets.Play(
    value=180,
    min=0,
    max=360,
    step=1,
    description="Press play",
    disabled=False
)

widgets.jslink((play, 'value'), (degrees_slider, 'value'));

main_block_116_000 = widgets.VBox([
    fig3d,
    widgets.HBox([
        play,
        degrees_slider
    ])
    ],
    layout=widgets.Layout(
        align_items='center'
    )
)

main_block_116_000

Azalpen teorikoak

Fase-trantsizioak

Azalpen guztiak Josu M. Igartua irakasleak gainbegiratu ditu.


Fase-trantsizio bat sistema batek bere ezaugarrietan jasaten duen aldaketa da, sistemaren potentzial termodinamikoen deribaturen batek ez-jarraitasun bat azaltzen duenean gertatzen dena [1].

Potentzial termodinamikoak sistemaren egoera-funtzioak konbinatzean lortzen diren magnitude eskalarrak dira, energiaren dimentsioak dituztenak [2]. Egoera-funtzio horien balioak sistemak konfigurazio-espazioan duen kokapenaren menpekoak baino ez dira. Ondorioz, konfigurazio-espazioko puntu batetik beste batera mugitzean, egoera-funtzioak jasandako aldaketa hasierako eta amaierako puntuen araberakoa da eta ez batetik bestera joateko jarraitutako bidearena.

Aipaturiko egoera-funtzioen artean \(U\) sistemaren barne-energia dago. Barne-energia \(p\), \(V\), \(T\) eta \(S\) egoera-funtzioekin konbinatzean lortzen dira potentzial termodinamikoak. Konbinazio posible guztietatik hiru bereziki erabilgarriak dira eta izen bereziak jasotzen dituzte: \(H = U + pV\) (entalpia), \(F = U - TS\) (Helmholtz-en energia) eta \(G = U + pV - TS\) (Gibbs-en energia).

Sistema baten egonkortasun-baldintzak potentzial termodinamikoen menpe idatzi daitezke: sistema bat egonkorra izan dadin potentzial horiek beraien aldagai intentsiboekiko ganbilak izan behar dira eta estentsiboekiko, aldiz, ahurrak (ikus \ref{egonk-1}, \ref{egonk-2} eta \ref{fas-1}. ekuazioak) [3].

\begin{equation}\label{egonk-1} \left(\frac{\partial^2 F}{\partial T^2}\right)_{V,N} \leq 0 \qquad \left(\frac{\partial^2 F}{\partial V^2}\right)_{T,N} \geq 0 \end{equation}
\begin{equation}\label{egonk-2} \left(\frac{\partial^2 H}{\partial S^2}\right)_{P,N} \geq 0 \qquad \left(\frac{\partial^2 F}{\partial V^2}\right)_{S,N} \leq 0 \end{equation}
\begin{equation}\label{fas-1} \left(\frac{\partial^2 G}{\partial T^2}\right)_{P,N} \leq 0 \qquad \left(\frac{\partial^2 G}{\partial P^2}\right)_{T,N} \leq 0 \end{equation}

Orokortasunik galdu gabe Gibbs-en energiaren (edota potentzial kimikoaren) kasua bakarrik azter daiteke. Sarritan potentzial hori erabiltzen da bere aldagai naturalak (\(p\) eta \(T\)) laborategian erraz manipulagarriak direlako. Jarraian egingo diren hausnarketa eta azalpenak beste potentzial bat oinarritzat hartuta emango liratekeenen analogoak dira.

Paul Ehrenfest-ek proposatutako sailkapenaren arabera [1], fase-trantsizio baten ordena ez-jarraitasuna agertzen duen \(G\)-ren (edota \(\mu\)-ren) ordena txikieneko deribatuaren ordena da. Gaur egun, aldiz, lehen ordenako trantsizioak eta trantsizio ‘’jarraituak’’ (lehen ordenatik goragoko trantsizioak) ezberdintzen dira. Izan ere, lehen eta bigarren ordenako trantsizioen arteko ezberdintasunak gainontzeko trantsizioen artekoak baino esanguratsuagoak dira.

Lehen ordenako fase-trantsizio bat lehen aipatutako egonkortasun-baldintzen hutsegitea bezala uler daiteke. Egonkortasun-baldintzak betetzen ez direnean, sistema bi fase ezberdin eta bereizgarrietan (edo gehiagotan) banatzen da. Bi fase horiek ezberdinak eta bereizgarriak izateak konfigurazio-espazioko bi puntu ezberdin eta urrunetan kokatuta daudela esan nahi du eta bien arteko trantsizioa berehala gertatzen dela. Kasu honetan, ‘’bat-batekotasun’’ honek ez dauka prozesuak denboran iraun dezakeenarekin zerikusirik: ‘’berehala’’ gertatzeak sistema egonkorrak diren tarteko egoera guztietatik kuasiestatikoki pasatu beharrean hasierako egoeratik zuzenean amaierakora igarotzen dela adierazi nahi du.

Gibbs-en energiaren ikuspuntutik, lehen ordenako trantsizioen bidez lotutako \(N\) fasetan aurki daitekeen sistema baten Gibbs-en energiak \(N\) minimo izango ditu. Azpimarragarria da minimo bakoitzak fase bati dagokiola eta minimo ugari egoteak ezinbestean egonkortasun baldintzen haustura suposatzen duela, bi minimoen artean aurkako kurbadura duen eremua baitago derrigor. Sistema beti agertuko da minimorik sakonenean eta konfigurazio-espaziotik mugitzean minimoen arteko sakonera erlatiboak aldatuz joango dira. Ondoz-ondoko bi minimoen arteko sakonera berdina denean, hau da, bi faseen Gibbs-en energiaren balioa berdina denean, sistema fase batetik bestera alda daiteke lehen ordenako trantsizio baten bidez.

Ehrenfest-en sailkapenean oinarrituta, trantsizio horretan kontserbatuko ez diren aldagaiak identifika daitezke. Lehen ordenako fase-trantsizio batean Gibbs-en energiaren lehenengo deribatua ez-jarraitua izango da. Partikula kopurua konstante mantentzen dela onartuz, ondorengo garapenak egin daitezke:

\begin{equation} \left(\frac{\partial G}{\partial p}\right)_{T,N} = \left(\frac{\partial (\mu N)}{\partial p}\right)_{T,N} = N \left(\frac{\partial \mu}{\partial p}\right)_{T,N} \end{equation}
\begin{equation} \left(\frac{\partial G}{\partial T}\right)_{p,N} = \left(\frac{\partial (\mu N)}{\partial T}\right)_{p,N} = N \left(\frac{\partial \mu}{\partial T}\right)_{p,N} \end{equation}

Potenzial kimikoaren adierazpen diferentziala erabiliz:

\begin{equation} d \mu = v \; dp - s \; dT \end{equation}
\begin{equation}\label{fas-2} \left(\frac{\partial \mu}{\partial p}\right)_{T,N} = v \qquad \left(\frac{\partial \mu}{\partial T}\right)_{p,N} = -s \end{equation}

Bolumen eta entropia molarrak (eta potentzial kimikoaren ordena altuagoko deribatuak) izango dira, beraz, lehen ordenako trantsizio batean bi faseen artean jarraituak izango ez diren ezaugarriak. Aipatu behar da hemengo garapena sistema hidrostatiko baten kasurako egin dela baina prozedura hau guztiz analogoa izango litzatekeela beste izaera bateko sistema baten kasuan (sistema magnetiko batean, esaterako [4]).

Sistema hidrostatikoaren egonkortasun-baldintzak sistemaren egoera-aldagaien menpe idatz daitezke, \ref{fas-1} eta \ref{fas-2}. adierazpenak erabilita:

\begin{equation}\label{egonk-dpdv} \left(\frac{\partial^2 \mu}{\partial p^2}\right)_{T,N} = \left(\frac{\partial v}{\partial p}\right)_{T,N} \leq 0 \end{equation}
\begin{equation} \left(\frac{\partial^2 \mu}{\partial T^2}\right)_{p,N} = \left(\frac{\partial s}{\partial T}\right)_{p,N} \leq 0 \end{equation}

Bigarren ordenako fase-trantsizioei dagokionez, ez dira egonkortasun-balditzak hausten eta, ondorioz, sistema fase batetik bestera egonkorrak diren egoeretatik pasatzen da, kuasiestatikoki. Bigarren ordenako (eta ordena altuagoko) fase-trantsizio baten bidez lotutako bi egoera konfigurazio-espazioan bata bestearen alboan kokatuta daude, bien arteko ezberdintasunak nahi bezain txikiak izanik.

Nahiz eta aurrerago kontzeptu horietan sakondu, aipagarria da kasu batzuetan posible dela sistema hasierako egoera batetik amaierakora lehen zein bigarren ordenako fase-trantsizioen bidez eramatea. Lehenengo kasuan, sistema ezegonkortu egin beharko da eta, ondorioz, fase-trantsizioa gertatzen den bitartean hasierako eta amaierako faseak aldi berean existituko dira. Bigarrenean aldiz, sistema guztia modu jarraituan aldatuz joango da, elkarren artean ia bereiztezinak diren egoeretatik pasatuz amaierako egoerara iritsi arte.

1

  1. irudia: Bigarren ordenako fase-trantsizioak azaltzeko sortutako irudia. Karratu bakoitzak koloreaz ezaugarritutako sistemaren egoera bati dagokio.

Bigarren ordenako fase-trantsizioen izaera argitzeko \ref{1}. irudiko adibidea prestatu da. Kutxa bakoitzak sistema beraren egoera ezberdin bat adierazten du, fase-aldagai bakarrez ezaugarritua: kolorea. Muturretako karratuek (A eta E) hasierako eta amaierako egoerak adierazten dituzte. A eta B-ren artean bigarren ordenako trantsizio bat gertatzen da, nahiz eta bi sistemak ia berdinak izan. Antzera gertatzen da B-C, C-D eta D-E trantsizioekin: konfigurazio-espazioan ondoan egonik, ezaugarri gehienak komunean dituzte. Hala ere, A eta E-ren arteko ezberdintasunak nabarmenak dira, sistemaren egoera oso gutxi (nahi bezain gutxi) aldatzen duten bigarren ordenako fase-trantsizioak etengabe jasatean sistema konfigurazio-espazioan aldenduta egon daitezkeen puntu batetik bestera mugi baitaiteke. Aipatzekoa da ere adibide honetan A-tik E-rako aldaketa zuzenean egiten duen fase-trantsizioa lehen ordenakoa izango litzatekeela, sistema elkarren artean bereizgarriak diren egoera batetik bestera joango bailitzake.

Lehen egindakoaren antzera, bigarren ordenako trantsizioetan ez-jarraitasunak agertuko dituzten aldagaiak identifika daitezke:

\begin{equation} \left(\frac{\partial^2 \mu}{\partial p^2}\right)_{T,N} = \left(\frac{\partial v}{\partial p}\right)_{T,N} = - v \; \kappa_T \end{equation}
\begin{equation} \left(\frac{\partial^2 \mu}{\partial T^2}\right)_{p,N} = \left(\frac{\partial s}{\partial T}\right)_{p,N} = c_p / T \end{equation}
\begin{equation} \left(\frac{\partial^2 \mu}{\partial p \partial T}\right)_{N} = \left(\frac{\partial v}{\partial T}\right)_{p,N} = v \; \alpha \end{equation}

Bigarren ordenako fase-trantsizio bat denez, bolumen molarra jarraitua izango da eta \(\kappa_T\) (konprimagarritasun isotermoa), \(c_p\) (presio konstantepeko bero kapazitatea) eta \(\alpha\) (zabalkuntza termikoaren koefizientea) ez-jarraituak izango dira [1].

Zenbait kasutan, badago lehen eta bigarren ordenako trantsizioak erlazionatzen dituen konfigurazio-espazioko puntu bat: puntu kritikoa. Bertan, Gibbs-en energia minimo ugari izatetik bakar bat izatera pasatzen da: lehen eta bigarren ordenako trantsizioen arteko muga adierazten du puntu horrek.

Une honetan interesgarria izan daiteke sistema baten fase-diagramari erreparatzea. Adibidez, 2. irudian ageri dena sistema hidrostatiko bati dagokio. Bertan, fase bakoitza egonkorra den guneak lerro batzuen bidez bereizita azaltzen dira. Lerro horiek koexistentzia kurba izenaz ezagutzen dira: bertan bi faseen potentzial kimikoak berdinak direnez, sistemaren bi faseak koexistitu egiten dira eta sistema kurba horiek zeharkatzean fase batetik bestera lehen ordenako trantsizio baten bidez pasatzen da.

2

  1. irudia: Solido, likido eta gas-faseak erakusten dituen uraren fase-diagrama. Lerro horizontalak presio atmosferikoa adierazten du eta puntu txurien bidez uraren irakite eta izozte-puntuak adierazi dira. Iturria: “Concepts in Thermal Physics” [1].

Diagraman bi puntu berezi azpimarratu behar dira: lehena, puntu hirukoitza, zeinetan koexistentzia kurba guztiak elkartzen diren eta, ondorioz, hiru faseak potentzial kimiko berdina agertzen duten. Bigarrena, aldiz, lehen aipatutako puntu kritikoa da, likido-gas kurbaren amaiera markatzen duena. Sistema bat kurba horretan zehar puntu hirukoitzetik puntu kritikora eramatean, fase bien entropia eta bolumen molarrak berdinduz joango dira. Puntu kritikora iristean, ezaugarri horiek guztiz identikoak izango dira bi faseetan eta, ondorioz, sistema osoa fase bakarrean agertuko da.

Antzeko zerbait gertatzen da Gibbs-en energiaren kasuan: puntu hirukoitzean sakonera berdineko hiru minimo ikusten diren bitartean, sistema likido-gas koexistentzia kurbatik gora mugitzen den heinean bi minimoen artean topa daitekeen maximoaren altuera txikituz joango da. Maximo hori guztiz desagertuko da sistema puntu kritikora iristean. Koexistentzia kurba puntu hortatik harago jarraituz gero, bertan gertatuko liratekeen fase-trantsizio guztiak bigarren ordenakoak izango lirateke.

Likido-gas koexistentzia kurbaren malda Clausius-Clapeyron-en ekuazioak ematen du (ikus \ref{clausius}. ekuazioa), kurban zehar bi faseen potentzial kimikoak berdinak izatetik ondorioztatzen dena [1].

\begin{equation}\label{clausius} \frac{d p}{d T} = \frac{s_2 - s_1}{v_2 - v_1} \end{equation}

Amaitzeko, aipagarria da solido-likido koexistentzia kurbak ez duela puntu kritikorik azaltzen eta, ondorioz, bi fase horien arteko trantsizioa lehen ordenakoa izango dela beti.

Bibliografia

[1] Stephen J. Blundell eta Katherine M. Blundell. Phase transition. In Concepts in Thermal Physics, 305-323 or. Oxford University Press, 2009.

[2] Stephen J. Blundell eta Katherine M. Blundell. Thermodynamic potentials. In Concepts in Thermal Physics, 164-167 or. Oxford University Press, 2009.

[3] Herbert B. Callen. Stability of thermodynamic system. In Thermodynamics and an Introduction to Thermostatistics, 207-208 or. Wiley, 2. edizioa, 1985.

[4] Yevgen Melikhov, R. L. Hadimani, eta Arun Raghunathan. Phenomenological modelling of first order phase transitions in magnetic systems. Journal of Applied Physics,115(18), 2014.

van der Waals-en egoera-ekuazioa

Azalpen guztiak Josu M. Igartua irakasleak gainbegiratu ditu.


Nahiz eta fase-trantsizioen atzean dagoen oinarri fisikoa potentzial kimikoaren topologia izan, sarritan sistema termodinakoen azterketa lerro isotermoen forman oinarritzen da. Jariakin baten kasuan, oso interesgarria da van der Waals-en egoera-ekuazio mekanikoa (ikus \ref{vdw-eq-1}. ekuazioa)betetzen duen jariakin errealaren kasua aztergaitzat hartzea.

\begin{equation}\label{vdw-eq-1} \left( p + \frac{a}{v^2} \right)\left(v - b \right) = R T \end{equation}

non \(a\) eta \(b\) van der Waals-en parametroak, \(v\) jariakinaren bolumen molarra, \(p\) presioa, \(T\) tenperatura eta \(R\) gas idealen konstantea diren.

Gas idealen egoera-ekuazio mekanikoaren moldaketa bat bezala uler daiteke J.D. van der Waals herbeheretar fisikariak bere tesian proposatutako ekuazioa [1].

\begin{equation}\label{vdw-eq-2} pv = RT \end{equation}

Gas idealen egoera-ekuazioa (ikus \ref{vdw-eq-2}. ekuazioa) gas partikulak puntualak eta elkarren artean elkarrekintzarik gabekoak izatetik ondorioztatzen da. Van der Waals-en egoera ekuazioak, aldiz, bi faktore horiek kontuan hartzen ditu gas idealaren ekuazioari bi atal gehituz: \(a\) parametroarekiko proportzionala denak partikulen arteko elkarrekintza integratzen du eta \(b\) parametroak, aldiz, partikulek espazioan betetzen duten bolumena adierazten du [3]. Konposatu bakoitzerako bi parametro horien balioa esperimentalki lor daiteke.

Van der Waals-en arabera, jariakina osatzen duten partikulen bolumena zein haien arteko elkarrekintza berdina da sistemaren likido eta gas faseetan eta bi faseetako portaera bere egoera-ekuazioaren bidez deskriba daiteke, ondorioz. Are gehiago, ekuazioa bi faseetan baliagarria denez, bien arteko trantsizioak deskribatzeko ere baliagarria izango da.

Aipatu beharra dago egoera-ekuazio horrek ez duela jariakin errealen portaera modu kuantitatibo batean azaltzen, baina bai erabilgarria dela azalpen kualitatibo edota erdi-kuantitatiboak emateko.

Funtzioaren analisi-matematikoa lerro isotermoetan oinarritutako azterketaren abiapuntua izan daiteke, proposatutako funtzioak ez baitauka esangura fisikorik bere izate-eremu guztian. Lerro isotermoen adierazpena \ref{vdw-eq-1}. ekuaziotik ondoriozta daiteke.

\begin{equation}\label{vdw-eq-3} p(v ; T) = \frac{RT}{v - b} - \frac{a}{v^2} \end{equation}

Begi-bistakoa denez, lerro isotermoen funtzioak polo bana du \(v=0\) eta \(v=b\) puntuetan, \ref{vdw-eq-3}. adierazpenak erakusten duen moduan. Edozein elementurentzat \(T\) tenperaturako lerro isotermoa \(v > 0\) eremuan irudikatuz (ikus \ref{vdw:1}. irudia) aipagarria da \(v \in (0, b)\) tartean \(\left(\partial p / \partial v \right)_{T} > 0\) dela. Sistema ezengonkorra da tarte horretan, egonkortasun-baldintzen arabera. Zonalde ezegonkor hori bi poloen artean mugatuta dagoenez, ez da posible sistema fisikoa eremu horretan egotea. Ondorioz, van der Waals-en egoera ekuazioak esangura fisikoa duen eremua \(v \in (b, \infty)\) da eta hori izango da hemendik aurrera aztertuko dena.

1

  1. irudia: van der Waals-en egoera-ekuaziotik lortutako lerro isotermoen itxura. Irudia 118-000 programarekin sortu da.

Eremu horren barruan lerro isotermo ezberdinak irudikatzean, nabarmena da bi zonalde ezberdintzen direla (ikus 2. irudia). Tenperatuta baxuetan agertzen den zonaldean lehen aipatutako egonkortasun-baldintzak huts egiten duen eremuak identifika daitezke eta, beraz, tenperatura horietan sistemak lehen ordenako fase-trantsizio bat jasaten duela ondoriozta daiteke. Tenperatura altuetan aldiz, sistema egonkorra da eremu guztian.

1

  1. irudia: van der Waals-en egoera-ekuaziotik lortutako lerro isotermoen itxura. Irudia 111-000 programarekin sortu da.

Puntu kritikoa

Van der Waals-en egoera-ekuazioak lehen ordenako fase-trantsizioak aurreikusten dituenez, onargarria da puntu kritikoaren existentzia ere aurreikusi dezakeela pentsatzea. Kasu honetan, nahikoa da hurrengo baldintzak aplikatzea puntu kritikoa kalkulatzeko (baldintza orokorra potentzial kimikoaren ordena guztietako deribatuak nuluak izatea da):

\begin{equation}\label{vdw-eq-4} \left( \frac{\partial^2 \mu}{\partial p^2}\right)_{T} = 0 \quad \text{eta} \quad v \equiv \left( \frac{\partial \mu}{\partial p}\right)_{T} \; \rightarrow \quad \left( \frac{\partial^2 \mu}{\partial p^2}\right)_{T} \equiv \left( \frac{\partial v}{\partial p}\right)_{T} = 0 \end{equation}
\begin{equation}\label{vdw-eq-45} \left( \frac{\partial^3 \mu}{\partial p^3}\right)_{T} = 0 \quad \rightarrow \quad \left( \frac{\partial^3 \mu}{\partial p^3}\right)_{T} \equiv \left( \frac{\partial^2 v}{\partial p^2}\right)_{T} = 0 \end{equation}

Puntu kritikoaren adierazpena \ref{vdw-eq-4} eta \ref{vdw-eq-45} adierazpenetako baldintzak \ref{vdw-eq-3}. ekuazioko lerro isotermoari aplikatzean lortzen da:

\begin{equation}\label{vdw-5} p_C = \frac{a}{27 b^2} \qquad v_C = 3b \qquad T_C = \frac{8 a}{27 b} \end{equation}

Tenperatura kritikoko lerro isotermoa lerro isotermo kritikoa izango da eta horrek bananduko ditu 2. irudian ageri diren bi zonaldeak [3].

Van der Waals-en egoera-ekuazioko \(a\) eta \(b\) parametroak deskribatutako jariakinaren partikulen bolumenarekin eta haien arteko elkarrekintzarekin erlazionatuta daudenez, jariakin bakoitzarentzat bakarrak eta bereizgarriak dira. Puntu kritikoaren kokapena bi parametro horien menpekoa baino ez denez, jariakin bakoitzaren puntu kritikoa konfigurazio-espazioko puntu ezberdin batean egongo da kokatuta. Hori bistaratzeko 113-000 programa garatu da: ohiko elementu eta konposatuen parametroen balioak jasotzeaz gain haien puntu kritikoak grafika ezberdinetan erakusten ditu (ikus 3. irudiko grafikak).

1 1

  1. irudia: Elementu eta konposatu batzuen puntu kritikoak. Parametroen balioak “CRC Handbook of Chemistry and Physics” liburutik atera dira [4]. Irudia 113-000 programarekin sortu da.

Puntu kritikoaren kokapena aztertutako jariakinaren menpekoa izateak, tenperatura berdinerako elementu bakoitzaren lerro isotermoek forma ezberdina azaltzea eragiten du (hori 113-000 programan ere behatu daiteke). Hala ere, behin puntu kritikoaren adierazpena ezagututa, posible da egoera-ekuazioa puntu kritikoaren menpe adieraztea:

\begin{equation} \left(p_R + \frac{3}{v_R^2}\right)\left(3 v_R - 1 \right) = 8 T_R \qquad \text{non} \qquad p_R = \frac{p}{p_C} \quad v_R = \frac{v}{v_C} \quad T_R = \frac{T}{T_C} \end{equation}

Ekuazio horri egoera-ekuazio laburtua deritzo eta jariakin guztietarako komuna da, ez baitu \(a\) eta \(b\) parametroekiko menpekotasun espliziturik [2]. Forma laburtua oso erabilgarria da adierazpen-grafikoak eta garapenak egiterako orduan, orokortasunik galdu gabe manipulatu beharreko parametroen kopurua murrizten baita eta, gainera, sistema guztiak aldi berean azaltzen baitira. Horregatik, hemendik aurrera adierazpen horretatik lortutako lerro isotermoak aztertuko dira (ikus \ref{vdw-8}. ekuazioa).

\begin{equation}\label{vdw-8} p_R \left(v_R, T_R \right) = \frac{8 T_R}{\left(3 v_R - 1 \right)} - \frac{3}{v_R^2} \end{equation}

Potentzial kimikoaren azterketa

Fase-trantsizioen inguruko teoria orokorrak van der Waals-en egoera-ekuazioan duen eragina aztertzeko, \(\mu\) potentzial kimikoaren adierazpena lortu behar da. Horretarako, \ref{vdw-6}. adierazpeneko Gibbs-Duhem-en erlaziotik abiatuz lerro isotermoan zehar tenperatura konstantea dela aplika daiteke integrala ebazteko:

\begin{equation}\label{vdw-6} d \mu = -s \; dT + v \; dp \; \xrightarrow{T = \text{kte}} \; \left( d \mu \right)_T = v \; \left( dp \right)_T \end{equation}
\begin{equation}\label{vdw-7} \mu = \int v \; dp + \phi (T) \end{equation}

non \ref{vdw-7}. adierazpeneko \(\phi (T)\) gaia integrazio-konstantea den, tenperaturaren menpekoa. Gai horren ondorioz, metodo horren bidez lortutako \(\mu = \mu(p, T)\) planoa ez da kuantitatiboki baliagarria izango, beti faltako baitu konstante horren ekarpena [2].

Zenbait tenperaturatan kokatutako sistementzat integral horren balioa kalkulatuz gero (117-000 programan lantzen den moduan), honelako \(\mu\)-ren adierazpenak lortzen dira:

4 5 6

  1. irudia: Tenperatura ezberdinetarako lortutako potentzial kimikoak. Irudiak 117-000 programarekin sortu dira.

Hiru adar bereiz daitezke 4. irudiko lehenengo grafikan: irudiko lehenengo grafikan: lehena, puntu baxuenetik hasita kurbadura negatiboarekin igotzen dena, gas egoerari dagokion potentzial kimikoa da. Bigarrena presio handietan bakarrik topa daitekeen kurba da, hau ere kurbadura negatiboduna eta likido egoeraren potentzial kimikoa adierazten duena.

Bi kurba horiek eskuragarri dauden presioetan kontuan izan behar da sistema beti bi kurbetatik \(\mu\) txikiena duenean egongo dela, aurreko atalean azaldu denez. Ondorioz, \(p\) txikietan sistema gas egoeran egongo da. Bi kurbak elkartzen diren puntuan bien potentzial kimikoak berdinak direnez, sistemak lehen ordenako fase-trantsizioa jasango du. Hortik aurrera sistema likido egoeran egongo da, horri dagokiona izango baita balio baxueneko potentzial kimikoak dituen kurba.

Bi adar horien artekoari dagokionez, azpimarragarria da bere kurbadurak besteek azaltzen dutenaren aurkako zeinua duela. Aurreko atalean aipatutako egonkortasun-baldintzei erreparatuz (ikus \ref{fas-1}. adierazpenak), argi gelditzen da gune horretan sistema ez dela egonkorra eta, ondorioz, sistemak ez duela inoiz hirugarren adar hori jarraituko. Ohartu, 4. irudian potentzial kimikoa aldagai intentsibo baten menpe adierazten dela eta, beraz, sistema egonkorra izan dadin bere bigarren deribatua negatiboa izan behar dela.

Beste bi grafiketan ageri denez, \(T \geq T_C\) kasuetan ez da hirugarren adar hori azaltzen. Ondorioz, sistema egonkorra da eremu guztian eta lehen ordenako fase-trantsizioak jasan beharrean, bigarren ordenakoak jasaten ditu.

Lerro isotermo errealak

Egonkortasun-baldintzak betetzen diren edozein puntutan topatu daiteke sistema; hau da: posiblea da sistema 5. irudiko C edo E puntuetan topatzea, nahiz eta potentzial kimikoa minimizatzen duten egoerak ez izan. Egoera metaegonkorrak dira horiek: ertan kokatutako sistema batek perturbazio txiki bat jasanez gero, bi fase bereizgarrietan banatuko da.

Sistema C puntuan dagoenean likido gain berotua egoeran dagoela esaten da eta E puntuan aldiz gas azpi hoztua egoeran [3].

Sistema ezin denez egoera ezegonkor batean egon, argi dago ez dela C puntutik E puntura \ref{vdw-7}. irudiko lerro isotermoan zehar joaten eta behin fase-trantsizioa hasita sistema beste lerro isotermo bati jarraitzen diola.

Ideia horretan sakontzeko,117-000 programan \(v = v(p, T)\) grafikako puntuak \(\mu = \mu(p, T)\) grafikako puntuekin zuzenenan erlazionatu daitezke, 7. irudian erakusten denez. Ezkerreko grafikako lerro isotermoan zehar 6. ekuazioko integrala kalkulatuz eskuineko irudiko potentzial kimikoa lor daiteke. Markatutako puntuek esangura berezia dute: lehen azaldutako C eta E puntuez gain, A eta G puntuek sistema fase bakarrean ageri deneko egoerak adierazten dituzte, B eta F puntuek lehen ordenado fase-trantsizioaren muga azaltzen dute eta D puntuak sistema ezegonkorra den egoera adierazten du.

1 1

  1. irudia: Potentzial kimikoaren eraikuntza erakusteko sortutako irudiak. Aukeratutako lerro isotermoa \(T = 0.9 \; T_C\) tenperaturari dagokio. Irudiak 117-000 programarekin sortu dira.

Azaldutako puntu berezi horiek ohiko lerro isotermoaren gainean proiektatu daitezke fase-trantsizioa hasten denean lerro isotermoan gertatzen dena aztertzeko.

  1. irudia: Tenperatura azpikritikodun lerro isotermoa, 5. irudiko grafikak sortzeko erabilitakoa. Koloreztatutako azalerak Maxwell-en eraikuntzari dagokio: sistema tenperatura batean finkatu ostean bi azalera horien balioak berdintzen presioan gertatuko da lehen ordenako fase trantsizioa. Irudia 111-000 programarekin sortu da.

Lehen aipatu denez, bi faseen potentzial kimikoak berdinak direnean gertatuko da lehen ordenako fase-trantsizioa. Ondorioz, B eta F puntuetan \(\mu_1 = \mu_2\) baldintza bete behar da, \(\mu_i\) fase bakoitzari dagokion potentzial kimikoa izanik.

\begin{equation} \mu_1 = \mu_2 \; \rightarrow \; \int_F^B v(p) dp = 0 \end{equation}

Integral hori bi zatitan bana daiteke:

\begin{equation} \int_F^D v(p) dp + \int_D^B v(p) dp = 0 \end{equation}

Hau da, 6. irudian koloreztatutako bi azalerak balioz berdinak izan behar dira. Arau horri Maxwell-en eraikuntza deritzo: sistemaren isoterma erreala trunkatutako isoterma ideala izango da [2].

Sistemaren tenperatura tenperatura kritikoa baino baxuagoa den kasuetan (6. irudikoa, esaterako), sistema G puntutik B puntura van der Waals-ek proposatutako lerro isotermoari jarraituz garatuko da, F-B tartea lerro isobarikoan zehar egingo du eta B puntutik A punturako bidea berriro ere hasierako lerrotik egingo du, trantsizio guztian egonkortasun-baldintza hautsi gabe. Sistema tenperatura kritikoaren gainetik finkatzean, bigarren ordenako fase-trantsizioa van der Waals-en isotermei jarraituz gertatuko da.

Hori hobeto azaltzeko, 111-000 programaren bidez 7. irudia sortu da. Bertan, zenbait tenperatura ezberdinetan kokatutako sistemek errealitatean izango luketen garapena azaltzen da, Maxwell-en eraikuntzaren bidez kalkulatutakoa. Gainera, \(T < T_C\) kasuetan van der Waals-en lerro isotermo idealak ere adierazten dira, opakutasun txikiagoko lerroen bitartez.

  1. irudia: Zenbait tenperaturatan kokatutako sistema hidrostatiko baten lerro isotermo errealak. Koloreztatutako azalerak Maxwell-en eraikuntzari dagokio: sistema tenperatura batean finkatu ostean bi azalera horien balioak berdintzen presioan gertatuko da lehen ordenako fase trantsizioa. Irudia 111-000 programarekin sortu da.

Azterketa hori sakontzeko, tenperatura gehiagotan ipin daiteke sistema, 8. irudian azaltzen den grafika lortuz. Lerro urdinak mugatutako guneari koexistentzia-gunea deritzo: bi fasetan banatuta agertu ohi da bertan kokatutako sistema [2]. Lerro berdeak, aldiz, egonkortasun-baldintzak betetzen ez diren gunea inguratzen du: ezinezkoa da, beraz, berdez koloreztatutako gunean sistema fase bakarrean topatzea. Zonalde urdinean egonkortasun-baldintzak betetzen direnez, baldintza egokiak betez gero, sistema osoa egoera metaegonkor batean ager daiteke [5]. Bi muga horien eta irudikatutako lerro isotermoen arteko ebakidurak puntuek adierazten dituzte.

  1. irudia: Zenbait tenperaturatan kokatutako sistema hidrostatiko baten lerro isotermo errealak. Koexistentzia-gunea urdinez koloreztatu da eta existentziarik gabeko gunea berdez. Koloretako puntuek bi muga horien eta lerro isotermoen arteko ebakidurak adierazten dituzte. Irudia 111-000 programarekin sortu da.

Bolumen molarraren aldaketa

Aurreko atalean azaldutako koexistentzia-gunearen mugak sistemaren fase bakoitzeko bolumen molarrak jakiteko erabil daitezke. Lehen aipatu denez, sistema lerro isotermoko eremu isobarikoan zehar garatuko da lehen ordenako fase-trantsizioa jasatean. Trantsizio hori gertatzen den bitartean, fase banatan agertzen den bolumenaren proportzioa palankaren erregela-ren bitartez kalkula daiteke (ikus \ref{palanka}. adierazpena) [2]. Sistemaren bolumen molar osoa \(v\) izanik eta \(v_g\) eta \(v_l\), hurrenez hurren, gas eta likido faseen bolumen molarrak badira, honakoa ondorizta daiteke:

\begin{equation} v = \frac{V}{N} \quad \rightarrow \quad V = v N = N(x_lv_l + x_gv_g) \end{equation}
\begin{equation}\label{palanka} x_l = \frac{v_g - v}{v_g - v_l} \qquad x_g = \frac{v - v_l}{v_g - v_l} \end{equation}

Bi adierazpen horiek oso erabilgarriak izan daitezke likido/gas fase-trantsizioa irudikatzeko, 112-000 programan egin den moduan. Aipaturiko programaz baliatuz 9. irudiko bi diagramak sortu dira. Ezkerrekoan \(T = 0.85 \; T_C\) tenperaturari dagokion sistemaren lerro isotermo esperimental eta teorikoa azaltzen dira (azken hau opakutasun gutxiagorekin). Bertako puntu urdinak sistemaren egoera adierazten du, jatorrizko programan lerro isotermo guztian zehar mugi daitekeena. Eskuinean, aldiz, laborategian behatuko litzatekeenaren diagrama sinplifikatu bat ageri da, puntu urdinak finkatutako egoerari dagokiona hain zuzen ere. Diagramako blokeen koloreak faseen bolumen molarrarekin erlazionatuta daude: urdin argiak gas-faseak betetzen duen bolumen molarra adierazten duen bitartean, ilunak likido-fasearena adierazten du.

1 1

  1. irudia: Laborategian sistema hidrostatiko baten fase-trantsizioa lantzean behatuko litzatekeena azaltzeko sortutako irudiak. Kasu honetan \(T = 0.85 \; T_C\) tenperaturan kokatu da sistema. Irudiak 112-000 programarekin sortu dira.

Bigarren grafika horrek jariakinaren portaera esperimentala aurreikusteko aukera ematen du. Presio txikietan sistema gas egoera egongo litzateke (urdin argia) eta presioa handitu ahala gasaren bolumen molarra txikituz joango litzateke fase-trantsizioa hasi arte. Une horretan likido tantak agertuko lirateke (urdin iluna) eta fase-trantsizioak iraun bitartean bi egoeren bolumenen arteko proportzioa palankaren erregelak emandakoa izango litzateke. Sistema osoa fasez aldatzean argi gelditzen da likidoaren konprimagarritasun isotermoa askoz txikiagoa dela, presio aldaketa berdinerako bolumenean eragindako aldaketa lehen baino txikiagoa baita.

Programa beraz baliatuz, sistema \(T > T_C\) tenperatura batean finkatu eta bigarren ordenako fase-trantsizioen ezaugarriak behatzea ere interesgarria izan daiteke. Kasu horretan, 10. irudian aurkeztutako diagramak lortzeko \(T = 1.2 \; T_C\) tenperaturan finkatu da sistema. Baldintza horren pean sistemak bigarren ordenako fase-trantsizioa jasaten duenez, ez dira aldi berean bi fase bereizi inoiz behatzen: presio baxuko egoera batetik hasi eta presioa pixkanaka handituz, diagramako sistema urdin argiz margotuta egotetik urdin ilunez margotuta egotera pasa da.

Horrek fase-trantsizioen inguruko mezu garrantzitsu bat ematen du, fase-trantsizioen inguruko atalean aipatutakoa: sistema hidrostatikoaren kasuan, lehen ordenako trantsizio batean bolumen molarrak jauzi bat aurkezten du eta bigarren ordenekoetan, aldiz, aipaturiko ezaugarria modu jarraituan garatzen da.

1 1

  1. irudia: Laborategian sistema hidrostatiko baten fase-trantsizioa lantzean behatuko litzatekeena azaltzeko sortutako irudiak. Kasu honetan \(T = 1.20 \; T_C\) tenperaturan kokatu da sistema. Irudiak 112-000 programarekin sortu dira.

Entropia molarraren aldaketa

Bolumen molarraz gain, entropia molarrak ere ez-jarraitasuna azaltzen du lehen ordenako fase-trantsizioetan. Entropiaren adierazpena bere definizioa eta \ref{vdw-8}. ekuazioan aurkeztutako lerro isotermoen adierazpena erabiliz kalkula daiteke.

\begin{equation}\label{vdw-eq-15} ds = \left( \frac{\partial p}{\partial T}\right)_{v} dv \quad \text{eta} \quad p_R \left(v_R, T_R \right) = \frac{8 T_R}{\left(3 v_R - 1 \right)} - \frac{3}{v_R^2} \; \rightarrow \; ds = \frac{8}{3v - 1} \end{equation}
\begin{equation} s = \int \frac{8}{3v - 1} dv = \frac{8}{3} \ln |3v - 1| + \text{kte} \end{equation}

Lortutako adierazpen horrek ez du entropiaren balio absolutua kalkulatzeko balio, baina bere itxura irudikatzeko erabil daiteke (ikus 11. irudia). Oraingoan, sistema tenperatura jakin batean finkatu beharrean presio konstateko baldintzetan mantendu da. Horrela, tenperatura aldatzean, presio eta tenperatura horri dagozkion bolumena hartuko du sistemak. Lehen ordenako fase-trantsizioari dagozkion presio eta tenperaturara iristean, bi bolumen izango ditu eskuragarri, bi faseei dagozkienak, hain zuzen ere. Entropia molarraren bi balio bereizgarri ere izan ahalko ditu, ondorioz. Trantsizioa gertatzen deneko tenperatura gaindituta, sistemak berriro ere entropia bakar bat izango du eskuragarri. Prozesu hau erakusteko 11D-000 programa sortu da, 11. irudiko grafikak egiteko aukera ematen duena.

1 1

  1. irudia: Presio konstantean mantendutako sistema baten entropiak tenperaturarekiko duen menpekotasuna azaltzeko sortutako irudiak. Kasu honetan \(T = 1.20 \; T_C\) tenperaturan kokatu da sistema. 11D-000 programarekin sortu dira.

Sistema presio konstantean mantentzen denez, urdinez markatutako lerro isobarikoaren eta bere tenperaturari dagokion lerro isotermoaren arteko ebakiduran egongo da. Ezkerreko grafikan ikusten denez, tenperatura eta presio jakinetan sistemak lehen ordenako fase-trantsizioa jasaten badu, ebakidura horretako puntuen kopurua infinitoa izango da eta sistema ezkerreko grafikan beltzez adierazitako bi egoeren nahasketa bezala agertuko da. Tenperatuta gehiago handituz gero, fase-trantsizioa amaituko da eta sistema osoa fase bakar batean agertuko da (kasu honetan gas egoeran).

Amaitzeko, aipagarria da orain arte azaldutako programa ezberdinak erabiliz fase-trantsizioan gertatutako bero sorra kalkula daitekeela (ikus \ref{vdw-17}. garapena) [2].

\begin{equation}\label{vdw-17} \delta Q = \left(\frac{\partial s}{\partial T}\right) \; \xrightarrow{T = \text{kte}} \; \Delta Q = \frac{1}{T} \; \Delta s = \frac{8}{3T} \ln \left\vert \frac{3v_l - 1}{3v_g - 1} \right\vert \end{equation}

Bibliografia

[1] J. D. van der Waals. On the Continuity of the Gaseous and Liquid States. Elsevier Science Publisher B.V., 2004.

[2] Herbert B. Callen. First-order phase transitions in single component systems. In Thermodynamics and an Introduction to Thermostatistics, 215-241 or. Wiley, 2. edizioa, 1985.

[3] Stephen J. Blundell eta Katherine M. Blundell. Real gases. In Concepts in Thermal Physics, 280–288 or. Oxford University Press, 2009.

[4] David R. Lide. Fluid properties. In CRC Handbook of Chemistry and Physics, 43 or. CRC Press, 84. edizioa, 2003.

[5] Pablo G. Debenedetti. Thermodynamics. In Metastable liquids: concepts and principles, Physical chemistry (Princeton, N.J.), 84–88 or. Princeton University Press,1996.

Indices and tables