#!/usr/bin/env python
# -*- coding: utf-8 -*-
# This file is part of the Cortix toolkit environment.
# https://cortix.org
import pickle
import logging
import numpy as np
import scipy.constants as const
from scipy.integrate import odeint
from cortix.src.module import Module
from cortix.support.phase import Phase
from cortix.support.specie import Specie
from cortix.support.quantity import Quantity
[docs]class Droplet(Module):
'''
Droplet Cortix module used to model very simple fluid-particle interactions.
Notes
-----
Port names used in this module: `external-flow` exchanges data with any other
module that provides information about the flow outside the droplet,
`visualization` sends data to a visualization module.
'''
[docs] def __init__(self):
'''
Attributes
----------
initial_time: float
end_time: float
time_step: float
show_time: tuple
Two-element tuple, `(bool,float)`, `True` will print to standard
output.
'''
super().__init__()
self.port_names_expected = ['external-flow','visualization']
self.initial_time = 0.0
self.end_time = 100
self.time_step = 0.1
self.show_time = (False,1*const.minute)
self.bounce = True
self.slip = True
species = list()
quantities = list()
self.ode_params = dict()
# Create a drop with random diameter up within 5 and 8 mm.
self.droplet_diameter = (np.random.random(1) * (8 - 5) + 5)[0] * const.milli
self.ode_params['droplet-diameter'] = self.droplet_diameter
self.ode_params['droplet-xsec-area'] = np.pi * (self.droplet_diameter/2.0)**2
self.ode_params['gravity'] = const.g
# Species in the liquid phase
water = Specie(name='water', formula_name='H2O(l)', phase='liquid', \
atoms=['2*H','O'])
water.massCC = 0.99965 # [g/cc]
water.massCCUnit = 'g/cc'
water.molarCCUnit = 'mole/cc'
species.append(water)
droplet_mass = 4/3 * np.pi * (self.droplet_diameter/2)**3 * water.massCC * \
const.gram / const.centi**3 # [kg]
self.ode_params['droplet-mass'] = droplet_mass
# Spatial position
x_0 = np.zeros(3)
position = Quantity(name='position', formalName='Pos.', unit='m', value=x_0)
quantities.append(position)
# Velocity
v_0 = np.zeros(3)
velocity = Quantity(name='velocity', formalName='Veloc.', unit='m/s', value=v_0)
quantities.append(velocity)
# Speed
speed = Quantity(name='speed', formalName='Speed', unit='m/s', value=0.0)
quantities.append(speed)
# Radial position
radial_pos = Quantity(name='radial-position', formalName='Radius', unit='m', \
value=np.linalg.norm(x_0[0:2]))
quantities.append(radial_pos)
# Liquid phase
self.liquid_phase = Phase(self.initial_time, time_unit='s', species=species, \
quantities=quantities)
self.liquid_phase.SetValue('water', water.massCC, self.initial_time)
# Domain box dimensions: LxLxH m^3 box with given H.
# Origin of cartesian coordinate system at the bottom of the box.
# z coordinate pointing upwards. -L <= x <= L, -L <= y <= L,
self.box_half_length = 250.0 # L [m]
self.box_height = 500.0 # H [m]
# Random positioning of the droplet constrained to a box sub-region.
x_0 = (2 * np.random.random(3) - np.ones(3)) * self.box_half_length / 4.0
x_0[2] = self.box_height
self.liquid_phase.SetValue('position', x_0, self.initial_time)
# Droplet Initial velocity = 0 -> placed still in the flow
self.liquid_phase.SetValue('velocity', np.array([0.0,0.0,0.0]), \
self.initial_time)
# Default value for the medium surrounding the droplet if data is not passed
# through a conneted port.
medium_mass_density = 0.1 * const.gram / const.centi**3 # [kg/m^3]
self.ode_params['medium-mass-density'] = medium_mass_density
medium_displaced_mass = 4/3 * np.pi * (self.droplet_diameter/2)**3 * \
medium_mass_density # [kg]
self.ode_params['medium-displaced-mass'] = medium_displaced_mass
medium_dyn_viscosity = 1.81e-5 # kg/(m s)
self.ode_params['medium-dyn-viscosity'] = medium_dyn_viscosity
[docs] def run(self, *args):
time = self.initial_time
self.bottom_impact = False
while time < self.end_time:
# Interactions in the external-flow port
#---------------------------------------
position = self.liquid_phase.GetValue('position')
self.send( (time,position), 'external-flow' )
(check_time, velocity,fluid_props) = self.recv( 'external-flow' )
assert abs(check_time-time) <= 1e-6
self.ode_params['flow-velocity'] = velocity
#medium_mass_density = fluid_props.mass_density # see Vortex
#medium_dyn_viscosity = fluid_props.dyn_viscosity # see Vortex
medium_mass_density = fluid_props[0]
medium_dyn_viscosity = fluid_props[1]
self.ode_params['medium-mass-density'] = medium_mass_density
medium_displaced_mass = 4/3 * np.pi * (self.droplet_diameter/2)**3 * \
medium_mass_density # [kg]
self.ode_params['medium-displaced-mass'] = medium_displaced_mass
self.ode_params['medium-dyn-viscosity'] = medium_dyn_viscosity
# Interactions in the visualization port
#---------------------------------------
self.send( position, 'visualization' )
# Evolve droplet state to next time stamp
#----------------------------------------
time = self.__step( time )
self.send('DONE', 'visualization') # this should not be needed: TODO
return
def __rhs_fn(self, u_vec, t, params):
drop_pos = u_vec[:3]
flow_velo = params['flow-velocity']
drop_velo = u_vec[3:]
relative_velo = drop_velo - flow_velo
relative_velo_mag = np.linalg.norm(relative_velo)
area = params['droplet-xsec-area']
diameter = params['droplet-diameter']
dyn_visco = params['medium-dyn-viscosity']
rho_flow = params['medium-mass-density']
# Calculate the friction factor
reynolds_num = rho_flow * relative_velo_mag * diameter / dyn_visco
if reynolds_num <= 0.0:
fric_factor = 0.0
elif reynolds_num < 0.1:
fric_factor = 24 / reynolds_num
elif reynolds_num >= 0.1 and reynolds_num < 6000.0:
fric_factor = (np.sqrt(24 / reynolds_num) + 0.5407)**2
elif reynolds_num >= 6000:
fric_factor = 0.44
drag = - fric_factor * area * rho_flow * relative_velo_mag * relative_velo / 2.0
gravity = params['gravity']
droplet_mass = params['droplet-mass']
medium_displaced_mass = params['medium-displaced-mass']
buoyant_force = (droplet_mass - medium_displaced_mass) * gravity
dt_u_0 = u_vec[3] # d_t u_1 = u_4
dt_u_3 = drag[0]/droplet_mass # d_t u_4 = f_1/m
dt_u_1 = u_vec[4] # d_t u_2 = u_5
dt_u_4 = drag[1]/droplet_mass # d_t u_5 = f_2/m
dt_u_2 = u_vec[5] # d_t u_3 = u_6
dt_u_5 = (drag[2] - buoyant_force)/droplet_mass # d_t u_6 = f_3/m
return [dt_u_0, dt_u_1, dt_u_2, dt_u_3, dt_u_4, dt_u_5]
def __step(self, time=0.0):
r'''
ODE IVP problem:
Given the initial data at :math:`t=0`,
:math:`(u_1(0),u_2(0),u_3(0)) = (x_0,x_1,x_2)`,
:math:`(u_4(0),u_5(0),u_6(0)) = (v_0,v_1,v_2) =
(\dot{u}_1(0),\dot{u}_2(0),\dot{u}_3(0))`,
solve :math:`\frac{\text{d}u}{\text{d}t} = f(u)` in the interval
:math:`0\le t \le t_f`.
When :math:`u_3(t)` is negative, bounce the droplet to a random height between
0 and :math:`1.0\,x_0` with no velocity, and continue the time integration until
:math:`t = t_f`.
Parameters
----------
time: float
Time in the droplet unit of time (seconds).
Returns
-------
None
'''
if not self.bottom_impact:
x_0 = self.liquid_phase.GetValue('position', time)
v_0 = self.liquid_phase.GetValue('velocity', time)
u_vec_0 = np.concatenate((x_0,v_0))
t_interval_sec = np.linspace(0.0, self.time_step, num=2)
(u_vec_hist, info_dict) = odeint(self.__rhs_fn,
u_vec_0, t_interval_sec,
args=( self.ode_params, ),
rtol=1e-4, atol=1e-8, mxstep=300,
full_output=True)
assert info_dict['message'] =='Integration successful.', \
'At time: %r; state: %r; message: %r; full output: %r'%\
(round(time,2), u_vec_0, info_dict['message'], info_dict)
u_vec = u_vec_hist[1,:] # solution vector at final time step
values = self.liquid_phase.GetRow(time) # values at previous time
time += self.time_step
self.liquid_phase.AddRow(time, values)
if not self.bottom_impact:
# Ground impact with bouncing drop
if u_vec[2] <= 0.0 and self.bounce:
position = self.liquid_phase.GetValue('position', self.initial_time)
bounced_position = position[2] * np.random.random(1)
u_vec[2] = bounced_position
u_vec[3:] = 0.0 # zero velocity
# Ground impact with no bouncing drop and slip velocity
elif u_vec[2] <= 0.0 and not self.bounce and self.slip:
u_vec[2] = 0.0 # don't bounce
# Ground impact with no bouncing drop and no velocity
elif u_vec[2] <= 0.0 and not self.bounce and not self.slip:
u_vec[2] = 0.0 # don't bounce
u_vec[3:] = 0.0 # zero velocity
self.bottom_impact = True
# Update current values
self.liquid_phase.SetValue('position', u_vec[0:3], time)
self.liquid_phase.SetValue('velocity', u_vec[3:], time)
self.liquid_phase.SetValue('speed', np.linalg.norm(u_vec[3:]), time)
self.liquid_phase.SetValue('radial-position', np.linalg.norm(u_vec[0:2]),
time)
return time