Module reaction_mechanizer.drawing.mechanism_reaction_visualizer
Contains tools to visualize a reaction.
Expand source code
"""Contains tools to visualize a reaction.
"""
from enum import Enum
from typing import Any, Dict, List, Tuple, Union
import typing
from reaction_mechanizer.pathway.reaction import DifferentialEquationModel, ReactionMechanism, SimpleStep
from scipy.integrate import odeint, solve_ivp
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import seaborn as sns
import pandas as pd
class ReactionEvent(Enum):
"""Enum for the possible reaction events, such as:
CHANGE_CONCENTRATION: how much the concentration of the species should be changed by.
Additional Info: Tuple(str: species of interest, float: change in concentration)
SET_CONCENTRATION: what to set the concentration of the species to.
Additional Info: Tuple(str: species of interest, float: new concentration)
SMOOTH_CHANGE_CONCENTRATION: TBD
"""
CHANGE_CONCENTRATION = 0
SET_CONCENTRATION = 1
SMOOTH_CHANGE_CONCENTRATION = 2
class ReactionVisualizer:
"""Visualier for either `SimpleStep` or `ReactionMechanism`
"""
def __init__(self, reaction: Union[SimpleStep, ReactionMechanism]):
"""Create a `ReactionVisualizer` object to model the progression of a reaction
Args:
reaction (Union[SimpleStep, ReactionMechanism]): The reaction object to model
"""
self.reaction: Union[SimpleStep, ReactionMechanism] = reaction
def get_states(self,
initial_state: Dict[str, float],
time_end: float,
number_steps: int,
initial_time: float = 0,
ode_override: Union[Dict[str, DifferentialEquationModel], None] = None) -> Any:
"""Get concentration of the species in this reaction, with model specifications given.
Args:
initial_state (Dict[str, float]): initial concentration of all species in reaction
time_end (float): The end time for this model
number_steps (int): The granularity of this model. The higher the number of steps, the more accurate the model.
initial_time (float, optional): The time to start the model at. Defaults to 0.
ode_override (Union[Dict[str, DifferentialEquationModel], None], optional):
Dictionary containing the species to override the differential equation of using the provided one. Defaults to None.
Returns:
Any: 2D array where the rows represent the concentrations of the species at different times
(between `initial_time` and `end_time` and using `number_steps`). The columns are the species in the order given by `initial_state`
"""
ode_dict: Dict[str, DifferentialEquationModel] = self.reaction.get_differential_equations()
ode_dict_temp = {key: ode_dict[key] for key in initial_state.keys()}
ode_dict = ode_dict_temp
if ode_override is not None:
for key, ode in ode_override.items():
ode_dict[key] = ode
ode_function = _get_simple_step_ode_function(ode_dict, list(initial_state.keys()))
times = np.linspace(initial_time, time_end, number_steps)
cur_state = list(initial_state.values())
return odeint(ode_function, cur_state, times)
def progress_reaction(self,
initial_state: Dict[str, float],
time_end: float,
number_steps: int,
events: Union[List[Tuple[float, ReactionEvent, Tuple[Any]]], None] = None,
out: Union[str, None] = None,
show_intermediates: bool = True) -> pd.DataFrame:
"""Generate model for reaction
Args:
initial_state (Dict[str, float]): initial concentration of all species in reaction
time_end (float): The end time for this model
number_steps (int): The granularity of this model. The higher the number of steps, the more accurate the model.
events (List[Tuple[float, ReactionEvent, Tuple[Any]]], optional):
The list of events to occur during a specified time in the reaction. \
A single event is represented by a tuple holding the time of the perturbation, the type of perturbation (`ReactionEvent`), \
and the additional information associated with the `ReactionEvent` selected. Defaults to None.
out (Union[str, None], optional):
If a string is added, a png visually representing the reaction is created at the specified location (and a `DataFrame` is returned). \
Otherwise, just the `DataFrame` is returned. Defaults to None.
show_intermediates (bool, optional): Whether to show the intermediate species in the graph (doesn't affect dataframe or `SimpleStep`s).
Returns:
pd.DataFrame: DataFrame representing the concentrations of the species in the reaction
"""
data: Any = np.ndarray((0, 0))
if events is not None:
sorted_events = (*sorted(events, key=lambda x: x[0]), (time_end, None, tuple()))
cur_state = dict(initial_state)
prev_time_point_discretized: float = 0
for time_point, reaction_event_type, additional_info in sorted_events:
# First, discretize the time points so that everything is measured to the granularity of "number_steps"
time_interval = time_end / number_steps
time_point_discretized = round(time_point / time_interval) * time_interval
cur_number_steps = round((time_point - prev_time_point_discretized) / time_end * number_steps)
cur_data = self.get_states(cur_state, time_point, cur_number_steps, initial_time=prev_time_point_discretized)
data = cur_data if len(data) == 0 else typing.cast(Any, np.concatenate([data, cur_data]))
if reaction_event_type == ReactionEvent.CHANGE_CONCENTRATION:
for index, key in enumerate(cur_state.keys()):
if key == additional_info[0]:
cur_state[key] = cur_data[-1, index] + additional_info[1]
else:
cur_state[key] = cur_data[-1, index]
elif reaction_event_type == ReactionEvent.SET_CONCENTRATION:
for index, key in enumerate(cur_state.keys()):
if key == additional_info[0]:
cur_state[key] = cur_data[-1, index] + additional_info[1]
else:
cur_state[key] = cur_data[-1, index]
elif reaction_event_type == ReactionEvent.SMOOTH_CHANGE_CONCENTRATION:
pass
prev_time_point_discretized = time_point_discretized
else:
data = self.get_states(initial_state, time_end, number_steps)
times = np.linspace(0, time_end, number_steps)
_, ax = plt.subplots()
plt.tight_layout()
if out:
dont_show: List[str] = []
if not show_intermediates and type(self.reaction) == ReactionMechanism:
dont_show.extend(self.reaction.get_intermediates())
for i, thing in enumerate(initial_state.keys()):
if thing not in dont_show:
sns.lineplot(x=times, y=data[:, i], label="$"+thing+"$", ax=ax)
ax.legend()
sns.despine(ax=ax)
ax.margins(x=0, y=0)
_, top = ax.get_ylim()
ax.set_ylim([0, top*1.05])
plt.savefig(str(out), bbox_inches="tight", dpi=600)
return pd.DataFrame({"Time": times, **{thing: data[:, i] for i, thing in enumerate(initial_state.keys())}})
def get_states_robust(self,
initial_state: Dict[str, float],
time_end: float,
initial_time: float = 0,
ode_override: Union[Dict[str, DifferentialEquationModel], None] = None,
**kwargs_solve_ivp) -> Any:
"""Get concentration of the species in this reaction, with model specifications given.
"get_states_robust" uses the more diverse solve_ivp numerical solver rather than odeint in "get_states"
Args:
initial_state (Dict[str, float]): initial concentration of all species in reaction
time_end (float): The end time for this model
number_steps (int): The granularity of this model. The higher the number of steps, the more accurate the model.
initial_time (float, optional): The time to start the model at. Defaults to 0.
ode_override (Union[Dict[str, DifferentialEquationModel], None], optional):
Dictionary containing the species to override the differential equation of using the provided one. Defaults to None.
Returns:
Any: 2D array where the rows represent the concentrations of the species at different times
(between `initial_time` and `end_time` and using `number_steps`). The columns are the species in the order given by `initial_state`
"""
ode_dict: Dict[str, DifferentialEquationModel] = self.reaction.get_differential_equations()
ode_dict_temp = {key: ode_dict[key] for key in initial_state.keys()}
ode_dict = ode_dict_temp
if ode_override is not None:
for key, ode in ode_override.items():
ode_dict[key] = ode
ode_function = _get_simple_step_ode_function(ode_dict, list(initial_state.keys()), inverse_cur_state_and_t=True)
cur_state = list(initial_state.values())
return solve_ivp(ode_function, (initial_time, time_end), cur_state, **kwargs_solve_ivp)
def progress_reaction_robust(self,
initial_state: Dict[str, float],
time_end: float,
events: Union[List[Tuple[float, ReactionEvent, Tuple[Any]]], None] = None,
out: Union[str, None] = None,
show_intermediates: bool = True,
**kwargs_solve_ivp) -> pd.DataFrame:
"""Generate model for reaction
Args:
initial_state (Dict[str, float]): initial concentration of all species in reaction
time_end (float): The end time for this model
number_steps (int): The granularity of this model. The higher the number of steps, the more accurate the model.
events (Union[List[Tuple[float, ReactionEvent, Tuple[Any]]], None], optional):
The list of events to occur during a specified time in the reaction. \
A single event is represented by a tuple holding the time of the perturbation, the type of perturbation (`ReactionEvent`), \
and the additional information associated with the `ReactionEvent` selected. Defaults to None.
out (Union[str, None], optional):
If a string is added, a png visually representing the reaction is created at the specified location (and a `DataFrame` is returned). \
Otherwise, just the `DataFrame` is returned. Defaults to None.
show_intermediates (bool, optional): Whether to show the intermediate species in the graph (doesn't affect dataframe or `SimpleStep`s).
Returns:
pd.DataFrame: DataFrame representing the concentrations of the species in the reaction
"""
data: Any = np.ndarray((0, 0))
if events is not None:
print(events)
sorted_events = (*sorted(events, key=lambda x: x[0]), (time_end, None, tuple()))
cur_state = dict(initial_state)
prev_time_point: float = 0
for time_point, reaction_event_type, additional_info in sorted_events:
cur_data = self.get_states_robust(cur_state, time_point, initial_time=prev_time_point, **kwargs_solve_ivp)
data = cur_data if len(data) == 0 else typing.cast(Any, np.concatenate([data, cur_data]))
if reaction_event_type == ReactionEvent.CHANGE_CONCENTRATION:
for index, key in enumerate(cur_state.keys()):
if key == additional_info[0]:
cur_state[key] = cur_data[-1, index] + additional_info[1]
else:
cur_state[key] = cur_data[-1, index]
elif reaction_event_type == ReactionEvent.SET_CONCENTRATION:
for index, key in enumerate(cur_state.keys()):
if key == additional_info[0]:
cur_state[key] = cur_data[-1, index] + additional_info[1]
else:
cur_state[key] = cur_data[-1, index]
elif reaction_event_type == ReactionEvent.SMOOTH_CHANGE_CONCENTRATION:
pass
prev_time_point = time_point
else:
data = self.get_states_robust(initial_state, time_end, **kwargs_solve_ivp)
_, ax = plt.subplots()
plt.tight_layout()
if out:
dont_show: List[str] = []
if not show_intermediates and type(self.reaction) == ReactionMechanism:
dont_show.extend(self.reaction.get_intermediates())
for i, thing in enumerate(initial_state.keys()):
if thing not in dont_show:
sns.lineplot(x=data.t, y=data.y[i, :], label="$"+thing+"$", ax=ax)
ax.legend()
sns.despine(ax=ax)
ax.margins(x=0, y=0)
_, top = ax.get_ylim()
ax.set_ylim([0, top*1.05])
plt.savefig(str(out), bbox_inches="tight", dpi=600)
return pd.DataFrame({"Time": data.t, **{thing: data.y[i, :] for i, thing in enumerate(initial_state.keys())}})
def animate_progress_reaction(self,
video_destination_no_extension: str,
video_length: float,
fps: int,
extension: str = "mp4",
uses_robust: bool = False,
**progress_reaction_args):
"""Generate video visualization for reaction
Args:
video_destination_no_extension (str): The path to store the video at
video_length (float): The length of the resulting video
fps (int): The fps of the video
extension (str, optional): The extensions of the video [example values: "mp4", "gif", "mov", etc]. Defaults to "mp4".
uses_robust (bool, optional): Whether or not to use "self.progress_reaction" or "self.progress_reaction_robust"
"""
df = self.progress_reaction(**progress_reaction_args) if not uses_robust else self.progress_reaction_robust(**progress_reaction_args)
if not progress_reaction_args.get("show_intermediates", True) and type(self.reaction) == ReactionMechanism:
df = df.drop(self.reaction.get_intermediates(), axis="columns")
writer = animation.writers["ffmpeg"](fps=fps, metadata={"artist": "ReactionMechanizer"}, bitrate=1800) # Non-python dependency!
_, dummy_ax = plt.subplots()
plt.tight_layout()
df2 = df.drop("Time", axis="columns").stack().reset_index() # level_1: species, 0: concentration values
df2 = df2.rename({"level_1": "Species", 0: "Concentration"}, axis="columns")
df2["Time"] = [cur_time for cur_time in df["Time"] for _ in df2["Species"].unique()]
sns.lineplot(x="Time", y="Concentration", data=df2, hue="Species", ax=dummy_ax)
_, top_y = dummy_ax.get_ylim()
_, top_x = dummy_ax.get_xlim()
new_fig, new_ax = plt.subplots()
data_1_item = df2.iloc[:len(df2["Species"].unique())]
cur_data: Dict[str, Dict[str, List[float]]] = \
{spec: {"x": [data_1_item["Time"].iloc[0]], "y": [data_1_item["Concentration"].iloc[i]]}
for i, spec in enumerate(df2["Species"].unique())}
sns.lineplot(x="Time", y="Concentration", data=data_1_item, hue="Species", ax=new_ax)
new_ax.set_ylim([0, top_y*1.05])
new_ax.set_xlim([0, top_x*1.05])
new_ax.margins(x=0, y=0)
sns.despine(ax=new_ax)
def animate(frame_index):
for i, spec in enumerate(df2["Species"].unique()):
cur_data[spec]["x"].append(df["Time"].iloc[int(frame_index/(video_length*fps)*df.shape[0])])
cur_data[spec]["y"].append(df[spec].iloc[int(frame_index/(video_length*fps)*df.shape[0])])
new_ax.get_lines()[i].set_data(cur_data[spec]["x"], cur_data[spec]["y"])
ani = animation.FuncAnimation(new_fig, animate, frames=video_length*fps, repeat=True)
ani.save(f"{video_destination_no_extension}.{extension}", writer=writer)
def _get_simple_step_ode_function(differential_equations: Dict[str, DifferentialEquationModel], state_order: List[str], inverse_cur_state_and_t: bool = False):
active_odes = {key: val.get_lambda() for key, val in differential_equations.items()}
def simple_step_ode_function(cur_state, t):
cur_state_order = state_order
dict_state = {thing: val for thing, val in zip(cur_state_order, cur_state)}
out = []
for _, ode in active_odes.items():
out.append(ode(**dict_state))
return out
def simple_step_ode_function_params_inversed(t, cur_state):
cur_state_order = state_order
dict_state = {thing: val for thing, val in zip(cur_state_order, cur_state)}
out = []
for _, ode in active_odes.items():
out.append(ode(**dict_state))
return out
return simple_step_ode_function if not inverse_cur_state_and_t else simple_step_ode_function_params_inversed
Classes
class ReactionEvent (value, names=None, *, module=None, qualname=None, type=None, start=1)
-
Enum for the possible reaction events, such as:
CHANGE_CONCENTRATION: how much the concentration of the species should be changed by. Additional Info: Tuple(str: species of interest, float: change in concentration)
SET_CONCENTRATION: what to set the concentration of the species to. Additional Info: Tuple(str: species of interest, float: new concentration)
SMOOTH_CHANGE_CONCENTRATION: TBD
Expand source code
class ReactionEvent(Enum): """Enum for the possible reaction events, such as: CHANGE_CONCENTRATION: how much the concentration of the species should be changed by. Additional Info: Tuple(str: species of interest, float: change in concentration) SET_CONCENTRATION: what to set the concentration of the species to. Additional Info: Tuple(str: species of interest, float: new concentration) SMOOTH_CHANGE_CONCENTRATION: TBD """ CHANGE_CONCENTRATION = 0 SET_CONCENTRATION = 1 SMOOTH_CHANGE_CONCENTRATION = 2
Ancestors
- enum.Enum
Class variables
var CHANGE_CONCENTRATION
var SET_CONCENTRATION
var SMOOTH_CHANGE_CONCENTRATION
class ReactionVisualizer (reaction: Union[SimpleStep, ReactionMechanism])
-
Visualier for either
SimpleStep
orReactionMechanism
Create a
ReactionVisualizer
object to model the progression of a reactionArgs
reaction
:Union[SimpleStep, ReactionMechanism]
- The reaction object to model
Expand source code
class ReactionVisualizer: """Visualier for either `SimpleStep` or `ReactionMechanism` """ def __init__(self, reaction: Union[SimpleStep, ReactionMechanism]): """Create a `ReactionVisualizer` object to model the progression of a reaction Args: reaction (Union[SimpleStep, ReactionMechanism]): The reaction object to model """ self.reaction: Union[SimpleStep, ReactionMechanism] = reaction def get_states(self, initial_state: Dict[str, float], time_end: float, number_steps: int, initial_time: float = 0, ode_override: Union[Dict[str, DifferentialEquationModel], None] = None) -> Any: """Get concentration of the species in this reaction, with model specifications given. Args: initial_state (Dict[str, float]): initial concentration of all species in reaction time_end (float): The end time for this model number_steps (int): The granularity of this model. The higher the number of steps, the more accurate the model. initial_time (float, optional): The time to start the model at. Defaults to 0. ode_override (Union[Dict[str, DifferentialEquationModel], None], optional): Dictionary containing the species to override the differential equation of using the provided one. Defaults to None. Returns: Any: 2D array where the rows represent the concentrations of the species at different times (between `initial_time` and `end_time` and using `number_steps`). The columns are the species in the order given by `initial_state` """ ode_dict: Dict[str, DifferentialEquationModel] = self.reaction.get_differential_equations() ode_dict_temp = {key: ode_dict[key] for key in initial_state.keys()} ode_dict = ode_dict_temp if ode_override is not None: for key, ode in ode_override.items(): ode_dict[key] = ode ode_function = _get_simple_step_ode_function(ode_dict, list(initial_state.keys())) times = np.linspace(initial_time, time_end, number_steps) cur_state = list(initial_state.values()) return odeint(ode_function, cur_state, times) def progress_reaction(self, initial_state: Dict[str, float], time_end: float, number_steps: int, events: Union[List[Tuple[float, ReactionEvent, Tuple[Any]]], None] = None, out: Union[str, None] = None, show_intermediates: bool = True) -> pd.DataFrame: """Generate model for reaction Args: initial_state (Dict[str, float]): initial concentration of all species in reaction time_end (float): The end time for this model number_steps (int): The granularity of this model. The higher the number of steps, the more accurate the model. events (List[Tuple[float, ReactionEvent, Tuple[Any]]], optional): The list of events to occur during a specified time in the reaction. \ A single event is represented by a tuple holding the time of the perturbation, the type of perturbation (`ReactionEvent`), \ and the additional information associated with the `ReactionEvent` selected. Defaults to None. out (Union[str, None], optional): If a string is added, a png visually representing the reaction is created at the specified location (and a `DataFrame` is returned). \ Otherwise, just the `DataFrame` is returned. Defaults to None. show_intermediates (bool, optional): Whether to show the intermediate species in the graph (doesn't affect dataframe or `SimpleStep`s). Returns: pd.DataFrame: DataFrame representing the concentrations of the species in the reaction """ data: Any = np.ndarray((0, 0)) if events is not None: sorted_events = (*sorted(events, key=lambda x: x[0]), (time_end, None, tuple())) cur_state = dict(initial_state) prev_time_point_discretized: float = 0 for time_point, reaction_event_type, additional_info in sorted_events: # First, discretize the time points so that everything is measured to the granularity of "number_steps" time_interval = time_end / number_steps time_point_discretized = round(time_point / time_interval) * time_interval cur_number_steps = round((time_point - prev_time_point_discretized) / time_end * number_steps) cur_data = self.get_states(cur_state, time_point, cur_number_steps, initial_time=prev_time_point_discretized) data = cur_data if len(data) == 0 else typing.cast(Any, np.concatenate([data, cur_data])) if reaction_event_type == ReactionEvent.CHANGE_CONCENTRATION: for index, key in enumerate(cur_state.keys()): if key == additional_info[0]: cur_state[key] = cur_data[-1, index] + additional_info[1] else: cur_state[key] = cur_data[-1, index] elif reaction_event_type == ReactionEvent.SET_CONCENTRATION: for index, key in enumerate(cur_state.keys()): if key == additional_info[0]: cur_state[key] = cur_data[-1, index] + additional_info[1] else: cur_state[key] = cur_data[-1, index] elif reaction_event_type == ReactionEvent.SMOOTH_CHANGE_CONCENTRATION: pass prev_time_point_discretized = time_point_discretized else: data = self.get_states(initial_state, time_end, number_steps) times = np.linspace(0, time_end, number_steps) _, ax = plt.subplots() plt.tight_layout() if out: dont_show: List[str] = [] if not show_intermediates and type(self.reaction) == ReactionMechanism: dont_show.extend(self.reaction.get_intermediates()) for i, thing in enumerate(initial_state.keys()): if thing not in dont_show: sns.lineplot(x=times, y=data[:, i], label="$"+thing+"$", ax=ax) ax.legend() sns.despine(ax=ax) ax.margins(x=0, y=0) _, top = ax.get_ylim() ax.set_ylim([0, top*1.05]) plt.savefig(str(out), bbox_inches="tight", dpi=600) return pd.DataFrame({"Time": times, **{thing: data[:, i] for i, thing in enumerate(initial_state.keys())}}) def get_states_robust(self, initial_state: Dict[str, float], time_end: float, initial_time: float = 0, ode_override: Union[Dict[str, DifferentialEquationModel], None] = None, **kwargs_solve_ivp) -> Any: """Get concentration of the species in this reaction, with model specifications given. "get_states_robust" uses the more diverse solve_ivp numerical solver rather than odeint in "get_states" Args: initial_state (Dict[str, float]): initial concentration of all species in reaction time_end (float): The end time for this model number_steps (int): The granularity of this model. The higher the number of steps, the more accurate the model. initial_time (float, optional): The time to start the model at. Defaults to 0. ode_override (Union[Dict[str, DifferentialEquationModel], None], optional): Dictionary containing the species to override the differential equation of using the provided one. Defaults to None. Returns: Any: 2D array where the rows represent the concentrations of the species at different times (between `initial_time` and `end_time` and using `number_steps`). The columns are the species in the order given by `initial_state` """ ode_dict: Dict[str, DifferentialEquationModel] = self.reaction.get_differential_equations() ode_dict_temp = {key: ode_dict[key] for key in initial_state.keys()} ode_dict = ode_dict_temp if ode_override is not None: for key, ode in ode_override.items(): ode_dict[key] = ode ode_function = _get_simple_step_ode_function(ode_dict, list(initial_state.keys()), inverse_cur_state_and_t=True) cur_state = list(initial_state.values()) return solve_ivp(ode_function, (initial_time, time_end), cur_state, **kwargs_solve_ivp) def progress_reaction_robust(self, initial_state: Dict[str, float], time_end: float, events: Union[List[Tuple[float, ReactionEvent, Tuple[Any]]], None] = None, out: Union[str, None] = None, show_intermediates: bool = True, **kwargs_solve_ivp) -> pd.DataFrame: """Generate model for reaction Args: initial_state (Dict[str, float]): initial concentration of all species in reaction time_end (float): The end time for this model number_steps (int): The granularity of this model. The higher the number of steps, the more accurate the model. events (Union[List[Tuple[float, ReactionEvent, Tuple[Any]]], None], optional): The list of events to occur during a specified time in the reaction. \ A single event is represented by a tuple holding the time of the perturbation, the type of perturbation (`ReactionEvent`), \ and the additional information associated with the `ReactionEvent` selected. Defaults to None. out (Union[str, None], optional): If a string is added, a png visually representing the reaction is created at the specified location (and a `DataFrame` is returned). \ Otherwise, just the `DataFrame` is returned. Defaults to None. show_intermediates (bool, optional): Whether to show the intermediate species in the graph (doesn't affect dataframe or `SimpleStep`s). Returns: pd.DataFrame: DataFrame representing the concentrations of the species in the reaction """ data: Any = np.ndarray((0, 0)) if events is not None: print(events) sorted_events = (*sorted(events, key=lambda x: x[0]), (time_end, None, tuple())) cur_state = dict(initial_state) prev_time_point: float = 0 for time_point, reaction_event_type, additional_info in sorted_events: cur_data = self.get_states_robust(cur_state, time_point, initial_time=prev_time_point, **kwargs_solve_ivp) data = cur_data if len(data) == 0 else typing.cast(Any, np.concatenate([data, cur_data])) if reaction_event_type == ReactionEvent.CHANGE_CONCENTRATION: for index, key in enumerate(cur_state.keys()): if key == additional_info[0]: cur_state[key] = cur_data[-1, index] + additional_info[1] else: cur_state[key] = cur_data[-1, index] elif reaction_event_type == ReactionEvent.SET_CONCENTRATION: for index, key in enumerate(cur_state.keys()): if key == additional_info[0]: cur_state[key] = cur_data[-1, index] + additional_info[1] else: cur_state[key] = cur_data[-1, index] elif reaction_event_type == ReactionEvent.SMOOTH_CHANGE_CONCENTRATION: pass prev_time_point = time_point else: data = self.get_states_robust(initial_state, time_end, **kwargs_solve_ivp) _, ax = plt.subplots() plt.tight_layout() if out: dont_show: List[str] = [] if not show_intermediates and type(self.reaction) == ReactionMechanism: dont_show.extend(self.reaction.get_intermediates()) for i, thing in enumerate(initial_state.keys()): if thing not in dont_show: sns.lineplot(x=data.t, y=data.y[i, :], label="$"+thing+"$", ax=ax) ax.legend() sns.despine(ax=ax) ax.margins(x=0, y=0) _, top = ax.get_ylim() ax.set_ylim([0, top*1.05]) plt.savefig(str(out), bbox_inches="tight", dpi=600) return pd.DataFrame({"Time": data.t, **{thing: data.y[i, :] for i, thing in enumerate(initial_state.keys())}}) def animate_progress_reaction(self, video_destination_no_extension: str, video_length: float, fps: int, extension: str = "mp4", uses_robust: bool = False, **progress_reaction_args): """Generate video visualization for reaction Args: video_destination_no_extension (str): The path to store the video at video_length (float): The length of the resulting video fps (int): The fps of the video extension (str, optional): The extensions of the video [example values: "mp4", "gif", "mov", etc]. Defaults to "mp4". uses_robust (bool, optional): Whether or not to use "self.progress_reaction" or "self.progress_reaction_robust" """ df = self.progress_reaction(**progress_reaction_args) if not uses_robust else self.progress_reaction_robust(**progress_reaction_args) if not progress_reaction_args.get("show_intermediates", True) and type(self.reaction) == ReactionMechanism: df = df.drop(self.reaction.get_intermediates(), axis="columns") writer = animation.writers["ffmpeg"](fps=fps, metadata={"artist": "ReactionMechanizer"}, bitrate=1800) # Non-python dependency! _, dummy_ax = plt.subplots() plt.tight_layout() df2 = df.drop("Time", axis="columns").stack().reset_index() # level_1: species, 0: concentration values df2 = df2.rename({"level_1": "Species", 0: "Concentration"}, axis="columns") df2["Time"] = [cur_time for cur_time in df["Time"] for _ in df2["Species"].unique()] sns.lineplot(x="Time", y="Concentration", data=df2, hue="Species", ax=dummy_ax) _, top_y = dummy_ax.get_ylim() _, top_x = dummy_ax.get_xlim() new_fig, new_ax = plt.subplots() data_1_item = df2.iloc[:len(df2["Species"].unique())] cur_data: Dict[str, Dict[str, List[float]]] = \ {spec: {"x": [data_1_item["Time"].iloc[0]], "y": [data_1_item["Concentration"].iloc[i]]} for i, spec in enumerate(df2["Species"].unique())} sns.lineplot(x="Time", y="Concentration", data=data_1_item, hue="Species", ax=new_ax) new_ax.set_ylim([0, top_y*1.05]) new_ax.set_xlim([0, top_x*1.05]) new_ax.margins(x=0, y=0) sns.despine(ax=new_ax) def animate(frame_index): for i, spec in enumerate(df2["Species"].unique()): cur_data[spec]["x"].append(df["Time"].iloc[int(frame_index/(video_length*fps)*df.shape[0])]) cur_data[spec]["y"].append(df[spec].iloc[int(frame_index/(video_length*fps)*df.shape[0])]) new_ax.get_lines()[i].set_data(cur_data[spec]["x"], cur_data[spec]["y"]) ani = animation.FuncAnimation(new_fig, animate, frames=video_length*fps, repeat=True) ani.save(f"{video_destination_no_extension}.{extension}", writer=writer)
Methods
def animate_progress_reaction(self, video_destination_no_extension: str, video_length: float, fps: int, extension: str = 'mp4', uses_robust: bool = False, **progress_reaction_args)
-
Generate video visualization for reaction
Args
video_destination_no_extension
:str
- The path to store the video at
video_length
:float
- The length of the resulting video
fps
:int
- The fps of the video
extension
:str
, optional- The extensions of the video [example values: "mp4", "gif", "mov", etc]. Defaults to "mp4".
uses_robust
:bool
, optional- Whether or not to use "self.progress_reaction" or "self.progress_reaction_robust"
Expand source code
def animate_progress_reaction(self, video_destination_no_extension: str, video_length: float, fps: int, extension: str = "mp4", uses_robust: bool = False, **progress_reaction_args): """Generate video visualization for reaction Args: video_destination_no_extension (str): The path to store the video at video_length (float): The length of the resulting video fps (int): The fps of the video extension (str, optional): The extensions of the video [example values: "mp4", "gif", "mov", etc]. Defaults to "mp4". uses_robust (bool, optional): Whether or not to use "self.progress_reaction" or "self.progress_reaction_robust" """ df = self.progress_reaction(**progress_reaction_args) if not uses_robust else self.progress_reaction_robust(**progress_reaction_args) if not progress_reaction_args.get("show_intermediates", True) and type(self.reaction) == ReactionMechanism: df = df.drop(self.reaction.get_intermediates(), axis="columns") writer = animation.writers["ffmpeg"](fps=fps, metadata={"artist": "ReactionMechanizer"}, bitrate=1800) # Non-python dependency! _, dummy_ax = plt.subplots() plt.tight_layout() df2 = df.drop("Time", axis="columns").stack().reset_index() # level_1: species, 0: concentration values df2 = df2.rename({"level_1": "Species", 0: "Concentration"}, axis="columns") df2["Time"] = [cur_time for cur_time in df["Time"] for _ in df2["Species"].unique()] sns.lineplot(x="Time", y="Concentration", data=df2, hue="Species", ax=dummy_ax) _, top_y = dummy_ax.get_ylim() _, top_x = dummy_ax.get_xlim() new_fig, new_ax = plt.subplots() data_1_item = df2.iloc[:len(df2["Species"].unique())] cur_data: Dict[str, Dict[str, List[float]]] = \ {spec: {"x": [data_1_item["Time"].iloc[0]], "y": [data_1_item["Concentration"].iloc[i]]} for i, spec in enumerate(df2["Species"].unique())} sns.lineplot(x="Time", y="Concentration", data=data_1_item, hue="Species", ax=new_ax) new_ax.set_ylim([0, top_y*1.05]) new_ax.set_xlim([0, top_x*1.05]) new_ax.margins(x=0, y=0) sns.despine(ax=new_ax) def animate(frame_index): for i, spec in enumerate(df2["Species"].unique()): cur_data[spec]["x"].append(df["Time"].iloc[int(frame_index/(video_length*fps)*df.shape[0])]) cur_data[spec]["y"].append(df[spec].iloc[int(frame_index/(video_length*fps)*df.shape[0])]) new_ax.get_lines()[i].set_data(cur_data[spec]["x"], cur_data[spec]["y"]) ani = animation.FuncAnimation(new_fig, animate, frames=video_length*fps, repeat=True) ani.save(f"{video_destination_no_extension}.{extension}", writer=writer)
def get_states(self, initial_state: Dict[str, float], time_end: float, number_steps: int, initial_time: float = 0, ode_override: Optional[Dict[str, DifferentialEquationModel]] = None) ‑> Any
-
Get concentration of the species in this reaction, with model specifications given.
Args
initial_state
:Dict[str, float]
- initial concentration of all species in reaction
time_end
:float
- The end time for this model
number_steps
:int
- The granularity of this model. The higher the number of steps, the more accurate the model.
initial_time
:float
, optional- The time to start the model at. Defaults to 0.
ode_override (Union[Dict[str, DifferentialEquationModel], None], optional): Dictionary containing the species to override the differential equation of using the provided one. Defaults to None.
Returns
Any
- 2D array where the rows represent the concentrations of the species at different times
(between
initial_time
andend_time
and usingnumber_steps
). The columns are the species in the order given byinitial_state
Expand source code
def get_states(self, initial_state: Dict[str, float], time_end: float, number_steps: int, initial_time: float = 0, ode_override: Union[Dict[str, DifferentialEquationModel], None] = None) -> Any: """Get concentration of the species in this reaction, with model specifications given. Args: initial_state (Dict[str, float]): initial concentration of all species in reaction time_end (float): The end time for this model number_steps (int): The granularity of this model. The higher the number of steps, the more accurate the model. initial_time (float, optional): The time to start the model at. Defaults to 0. ode_override (Union[Dict[str, DifferentialEquationModel], None], optional): Dictionary containing the species to override the differential equation of using the provided one. Defaults to None. Returns: Any: 2D array where the rows represent the concentrations of the species at different times (between `initial_time` and `end_time` and using `number_steps`). The columns are the species in the order given by `initial_state` """ ode_dict: Dict[str, DifferentialEquationModel] = self.reaction.get_differential_equations() ode_dict_temp = {key: ode_dict[key] for key in initial_state.keys()} ode_dict = ode_dict_temp if ode_override is not None: for key, ode in ode_override.items(): ode_dict[key] = ode ode_function = _get_simple_step_ode_function(ode_dict, list(initial_state.keys())) times = np.linspace(initial_time, time_end, number_steps) cur_state = list(initial_state.values()) return odeint(ode_function, cur_state, times)
def get_states_robust(self, initial_state: Dict[str, float], time_end: float, initial_time: float = 0, ode_override: Optional[Dict[str, DifferentialEquationModel]] = None, **kwargs_solve_ivp) ‑> Any
-
Get concentration of the species in this reaction, with model specifications given. "get_states_robust" uses the more diverse solve_ivp numerical solver rather than odeint in "get_states"
Args
initial_state
:Dict[str, float]
- initial concentration of all species in reaction
time_end
:float
- The end time for this model
number_steps
:int
- The granularity of this model. The higher the number of steps, the more accurate the model.
initial_time
:float
, optional- The time to start the model at. Defaults to 0.
ode_override (Union[Dict[str, DifferentialEquationModel], None], optional): Dictionary containing the species to override the differential equation of using the provided one. Defaults to None.
Returns
Any
- 2D array where the rows represent the concentrations of the species at different times
(between
initial_time
andend_time
and usingnumber_steps
). The columns are the species in the order given byinitial_state
Expand source code
def get_states_robust(self, initial_state: Dict[str, float], time_end: float, initial_time: float = 0, ode_override: Union[Dict[str, DifferentialEquationModel], None] = None, **kwargs_solve_ivp) -> Any: """Get concentration of the species in this reaction, with model specifications given. "get_states_robust" uses the more diverse solve_ivp numerical solver rather than odeint in "get_states" Args: initial_state (Dict[str, float]): initial concentration of all species in reaction time_end (float): The end time for this model number_steps (int): The granularity of this model. The higher the number of steps, the more accurate the model. initial_time (float, optional): The time to start the model at. Defaults to 0. ode_override (Union[Dict[str, DifferentialEquationModel], None], optional): Dictionary containing the species to override the differential equation of using the provided one. Defaults to None. Returns: Any: 2D array where the rows represent the concentrations of the species at different times (between `initial_time` and `end_time` and using `number_steps`). The columns are the species in the order given by `initial_state` """ ode_dict: Dict[str, DifferentialEquationModel] = self.reaction.get_differential_equations() ode_dict_temp = {key: ode_dict[key] for key in initial_state.keys()} ode_dict = ode_dict_temp if ode_override is not None: for key, ode in ode_override.items(): ode_dict[key] = ode ode_function = _get_simple_step_ode_function(ode_dict, list(initial_state.keys()), inverse_cur_state_and_t=True) cur_state = list(initial_state.values()) return solve_ivp(ode_function, (initial_time, time_end), cur_state, **kwargs_solve_ivp)
def progress_reaction(self, initial_state: Dict[str, float], time_end: float, number_steps: int, events: Optional[List[Tuple[float, ReactionEvent, Tuple[Any]]]] = None, out: Optional[str] = None, show_intermediates: bool = True) ‑> pandas.core.frame.DataFrame
-
Generate model for reaction
Args
initial_state
:Dict[str, float]
- initial concentration of all species in reaction
time_end
:float
- The end time for this model
number_steps
:int
- The granularity of this model. The higher the number of steps, the more accurate the model.
- events (List[Tuple[float, ReactionEvent, Tuple[Any]]], optional):
- The list of events to occur during a specified time in the reaction.
A single event is represented by a tuple holding the time of the perturbation, the type of perturbation (
ReactionEvent
), and the additional information associated with theReactionEvent
selected. Defaults to None. - out (Union[str, None], optional):
- If a string is added, a png visually representing the reaction is created at the specified location (and a
DataFrame
is returned). Otherwise, just theDataFrame
is returned. Defaults to None. show_intermediates
:bool
, optional- Whether to show the intermediate species in the graph (doesn't affect dataframe or
SimpleStep
s).
Returns
pd.DataFrame
- DataFrame representing the concentrations of the species in the reaction
Expand source code
def progress_reaction(self, initial_state: Dict[str, float], time_end: float, number_steps: int, events: Union[List[Tuple[float, ReactionEvent, Tuple[Any]]], None] = None, out: Union[str, None] = None, show_intermediates: bool = True) -> pd.DataFrame: """Generate model for reaction Args: initial_state (Dict[str, float]): initial concentration of all species in reaction time_end (float): The end time for this model number_steps (int): The granularity of this model. The higher the number of steps, the more accurate the model. events (List[Tuple[float, ReactionEvent, Tuple[Any]]], optional): The list of events to occur during a specified time in the reaction. \ A single event is represented by a tuple holding the time of the perturbation, the type of perturbation (`ReactionEvent`), \ and the additional information associated with the `ReactionEvent` selected. Defaults to None. out (Union[str, None], optional): If a string is added, a png visually representing the reaction is created at the specified location (and a `DataFrame` is returned). \ Otherwise, just the `DataFrame` is returned. Defaults to None. show_intermediates (bool, optional): Whether to show the intermediate species in the graph (doesn't affect dataframe or `SimpleStep`s). Returns: pd.DataFrame: DataFrame representing the concentrations of the species in the reaction """ data: Any = np.ndarray((0, 0)) if events is not None: sorted_events = (*sorted(events, key=lambda x: x[0]), (time_end, None, tuple())) cur_state = dict(initial_state) prev_time_point_discretized: float = 0 for time_point, reaction_event_type, additional_info in sorted_events: # First, discretize the time points so that everything is measured to the granularity of "number_steps" time_interval = time_end / number_steps time_point_discretized = round(time_point / time_interval) * time_interval cur_number_steps = round((time_point - prev_time_point_discretized) / time_end * number_steps) cur_data = self.get_states(cur_state, time_point, cur_number_steps, initial_time=prev_time_point_discretized) data = cur_data if len(data) == 0 else typing.cast(Any, np.concatenate([data, cur_data])) if reaction_event_type == ReactionEvent.CHANGE_CONCENTRATION: for index, key in enumerate(cur_state.keys()): if key == additional_info[0]: cur_state[key] = cur_data[-1, index] + additional_info[1] else: cur_state[key] = cur_data[-1, index] elif reaction_event_type == ReactionEvent.SET_CONCENTRATION: for index, key in enumerate(cur_state.keys()): if key == additional_info[0]: cur_state[key] = cur_data[-1, index] + additional_info[1] else: cur_state[key] = cur_data[-1, index] elif reaction_event_type == ReactionEvent.SMOOTH_CHANGE_CONCENTRATION: pass prev_time_point_discretized = time_point_discretized else: data = self.get_states(initial_state, time_end, number_steps) times = np.linspace(0, time_end, number_steps) _, ax = plt.subplots() plt.tight_layout() if out: dont_show: List[str] = [] if not show_intermediates and type(self.reaction) == ReactionMechanism: dont_show.extend(self.reaction.get_intermediates()) for i, thing in enumerate(initial_state.keys()): if thing not in dont_show: sns.lineplot(x=times, y=data[:, i], label="$"+thing+"$", ax=ax) ax.legend() sns.despine(ax=ax) ax.margins(x=0, y=0) _, top = ax.get_ylim() ax.set_ylim([0, top*1.05]) plt.savefig(str(out), bbox_inches="tight", dpi=600) return pd.DataFrame({"Time": times, **{thing: data[:, i] for i, thing in enumerate(initial_state.keys())}})
def progress_reaction_robust(self, initial_state: Dict[str, float], time_end: float, events: Optional[List[Tuple[float, ReactionEvent, Tuple[Any]]]] = None, out: Optional[str] = None, show_intermediates: bool = True, **kwargs_solve_ivp) ‑> pandas.core.frame.DataFrame
-
Generate model for reaction
Args
initial_state
:Dict[str, float]
- initial concentration of all species in reaction
time_end
:float
- The end time for this model
number_steps
:int
- The granularity of this model. The higher the number of steps, the more accurate the model.
- events (Union[List[Tuple[float, ReactionEvent, Tuple[Any]]], None], optional):
- The list of events to occur during a specified time in the reaction.
A single event is represented by a tuple holding the time of the perturbation, the type of perturbation (
ReactionEvent
), and the additional information associated with theReactionEvent
selected. Defaults to None. - out (Union[str, None], optional):
- If a string is added, a png visually representing the reaction is created at the specified location (and a
DataFrame
is returned). Otherwise, just theDataFrame
is returned. Defaults to None. show_intermediates
:bool
, optional- Whether to show the intermediate species in the graph (doesn't affect dataframe or
SimpleStep
s).
Returns
pd.DataFrame
- DataFrame representing the concentrations of the species in the reaction
Expand source code
def progress_reaction_robust(self, initial_state: Dict[str, float], time_end: float, events: Union[List[Tuple[float, ReactionEvent, Tuple[Any]]], None] = None, out: Union[str, None] = None, show_intermediates: bool = True, **kwargs_solve_ivp) -> pd.DataFrame: """Generate model for reaction Args: initial_state (Dict[str, float]): initial concentration of all species in reaction time_end (float): The end time for this model number_steps (int): The granularity of this model. The higher the number of steps, the more accurate the model. events (Union[List[Tuple[float, ReactionEvent, Tuple[Any]]], None], optional): The list of events to occur during a specified time in the reaction. \ A single event is represented by a tuple holding the time of the perturbation, the type of perturbation (`ReactionEvent`), \ and the additional information associated with the `ReactionEvent` selected. Defaults to None. out (Union[str, None], optional): If a string is added, a png visually representing the reaction is created at the specified location (and a `DataFrame` is returned). \ Otherwise, just the `DataFrame` is returned. Defaults to None. show_intermediates (bool, optional): Whether to show the intermediate species in the graph (doesn't affect dataframe or `SimpleStep`s). Returns: pd.DataFrame: DataFrame representing the concentrations of the species in the reaction """ data: Any = np.ndarray((0, 0)) if events is not None: print(events) sorted_events = (*sorted(events, key=lambda x: x[0]), (time_end, None, tuple())) cur_state = dict(initial_state) prev_time_point: float = 0 for time_point, reaction_event_type, additional_info in sorted_events: cur_data = self.get_states_robust(cur_state, time_point, initial_time=prev_time_point, **kwargs_solve_ivp) data = cur_data if len(data) == 0 else typing.cast(Any, np.concatenate([data, cur_data])) if reaction_event_type == ReactionEvent.CHANGE_CONCENTRATION: for index, key in enumerate(cur_state.keys()): if key == additional_info[0]: cur_state[key] = cur_data[-1, index] + additional_info[1] else: cur_state[key] = cur_data[-1, index] elif reaction_event_type == ReactionEvent.SET_CONCENTRATION: for index, key in enumerate(cur_state.keys()): if key == additional_info[0]: cur_state[key] = cur_data[-1, index] + additional_info[1] else: cur_state[key] = cur_data[-1, index] elif reaction_event_type == ReactionEvent.SMOOTH_CHANGE_CONCENTRATION: pass prev_time_point = time_point else: data = self.get_states_robust(initial_state, time_end, **kwargs_solve_ivp) _, ax = plt.subplots() plt.tight_layout() if out: dont_show: List[str] = [] if not show_intermediates and type(self.reaction) == ReactionMechanism: dont_show.extend(self.reaction.get_intermediates()) for i, thing in enumerate(initial_state.keys()): if thing not in dont_show: sns.lineplot(x=data.t, y=data.y[i, :], label="$"+thing+"$", ax=ax) ax.legend() sns.despine(ax=ax) ax.margins(x=0, y=0) _, top = ax.get_ylim() ax.set_ylim([0, top*1.05]) plt.savefig(str(out), bbox_inches="tight", dpi=600) return pd.DataFrame({"Time": data.t, **{thing: data.y[i, :] for i, thing in enumerate(initial_state.keys())}})