Skip to content

Commit 44e7d5e

Browse files
author
Philipp Schneider
committed
added 3d plot option
1 parent ab3516c commit 44e7d5e

File tree

5 files changed

+416
-402
lines changed

5 files changed

+416
-402
lines changed

conda-recipe/meta.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
{% set version = '0.38' %}
1+
{% set version = '0.33' %}
22

33
package:
44
name: straindesign

doc/plot.png

250 KB
Loading

straindesign/lptools.py

Lines changed: 143 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,15 @@
88
from straindesign.names import *
99
from typing import Dict, Tuple
1010
from pandas import DataFrame
11-
from numpy import floor, sign, mod, nan, isnan, unique, inf, isinf, zeros, full, linspace, prod
11+
from numpy import floor, sign, mod, nan, isnan, unique, inf, isinf, full, linspace, \
12+
prod, array, mean, flip, ceil, floor
13+
from numpy.linalg import matrix_rank
1214
from os import cpu_count
1315
from contextlib import redirect_stdout, redirect_stderr
1416
from io import StringIO
1517
import matplotlib.pyplot as plt
16-
from scipy.spatial import ConvexHull
17-
import mpl_toolkits.mplot3d as a3
18+
from scipy.spatial import ConvexHull, Delaunay
19+
from matplotlib.cm import ScalarMappable, get_cmap
1820

1921
from straindesign.parse_constr import linexpr2mat, linexprdict2str
2022

@@ -496,13 +498,13 @@ def yopt(model,**kwargs):
496498
else:
497499
status = INFEASIBLE
498500

499-
def phase_plane(model, axes, **kwargs):
501+
def plot_flux_space(model, axes, **kwargs):
500502
reaction_ids = model.reactions.list_attr("id")
501503

502504
if CONSTRAINTS in kwargs:
503505
kwargs[CONSTRAINTS] = parse_constraints(kwargs[CONSTRAINTS],reaction_ids)
504506
else:
505-
kwargs[CONSTRAINTS] = None
507+
kwargs[CONSTRAINTS] = []
506508

507509
if SOLVER not in kwargs:
508510
kwargs[SOLVER] = None
@@ -513,7 +515,7 @@ def phase_plane(model, axes, **kwargs):
513515
else:
514516
show = True
515517

516-
axes = list(axes) # cast to list of lists
518+
axes = [list(ax) if not isinstance(ax,str) else [ax] for ax in axes] # cast to list of lists
517519
num_axes = len(axes)
518520
if num_axes not in [2,3]:
519521
raise Exception('Please define 2 or 3 axes as a list of tuples [ax1, ax2, (optional) ax3] with ax1 = (den,num).\n'+\
@@ -529,28 +531,54 @@ def phase_plane(model, axes, **kwargs):
529531

530532
ax_name = ["" for _ in range(num_axes)]
531533
ax_limits = [(nan,nan) for _ in range(num_axes)]
534+
ax_type = ["" for _ in range(num_axes)]
532535
for i,ax in enumerate(axes):
533-
ax = parse_linexpr(ax,reaction_ids)[0]
534-
ax_name[i] = linexprdict2str(ax)
535-
sol_min = fba(model,obj=ax,constraints=kwargs[CONSTRAINTS],solver=solver,obj_sense='minimize')
536-
sol_max = fba(model,obj=ax,constraints=kwargs[CONSTRAINTS],solver=solver,obj_sense='maximize')
537-
# abort if any of the fluxes are unbounded or undefined
538-
unbnd = [i+1 for i,v in enumerate([sol_min,sol_max]) if v.status == UNBOUNDED]
539-
if any(unbnd):
540-
raise Exception('One of the specified reactions is unbounded or undefined. Phase plane cannot be generated.')
541-
ax_limits[i] = [min((0,sol_min.objective_value)),max((0,sol_max.objective_value))]
542-
axes[i] = ax
536+
if len(ax) == 1:
537+
ax_type[i] = 'rate'
538+
elif len(ax) == 2:
539+
ax_type[i] = 'yield'
540+
if ax_type[i] == 'rate':
541+
ax[0] = parse_linexpr(ax[0],reaction_ids)[0]
542+
ax_name[i] = linexprdict2str(ax[0])
543+
sol_min = fba(model,obj=ax[0],constraints=kwargs[CONSTRAINTS],solver=solver,obj_sense='minimize')
544+
sol_max = fba(model,obj=ax[0],constraints=kwargs[CONSTRAINTS],solver=solver,obj_sense='maximize')
545+
# abort if any of the fluxes are unbounded or undefined
546+
inval = [i+1 for i,v in enumerate([sol_min,sol_max]) if v.status == UNBOUNDED or v.status == INFEASIBLE]
547+
if any(inval):
548+
raise Exception('One of the specified reactions is unbounded or problem is infeasible. Plot cannot be generated.')
549+
ax_limits[i] = [min((0,sol_min.objective_value)),max((0,sol_max.objective_value))]
550+
elif ax_type[i] == 'yield':
551+
ax[0] = parse_linexpr(ax[0],reaction_ids)[0]
552+
ax[1] = parse_linexpr(ax[1],reaction_ids)[0]
553+
ax_name[i] = '('+linexprdict2str(ax[0])+') / ('+linexprdict2str(ax[1])+')'
554+
sol_min = yopt(model,obj_num=ax[0],obj_den=ax[1],constraints=kwargs[CONSTRAINTS],solver=solver,obj_sense='minimize')
555+
sol_max = yopt(model,obj_num=ax[0],obj_den=ax[1],constraints=kwargs[CONSTRAINTS],solver=solver,obj_sense='maximize')
556+
# abort if any of the yields are unbounded or undefined
557+
inval = [i+1 for i,v in enumerate([sol_min,sol_max]) if v.status == UNBOUNDED or v.status == INFEASIBLE]
558+
if any(inval):
559+
raise Exception('One of the specified yields is unbounded or undefined or problem is infeasible. Plot cannot be generated.')
560+
ax_limits[i] = [min((0,ceil_dec(sol_min.objective_value,9))),max((0,floor_dec(sol_max.objective_value,9)))]
543561

544562
# compute points
545563
x_space = linspace(ax_limits[0][0], ax_limits[0][1], num=points)
546564
lb = full(points, nan)
547565
ub = full(points, nan)
548566
for i,x in enumerate(x_space):
549-
constr = [axes[0],'=',x]
550-
sol_vmin = fba(model,constraints=constr,obj=axes[1],obj_sense='minimize')
551-
lb[i] = sol_vmin.objective_value
552-
sol_vmax = fba(model,constraints=constr,obj=axes[1],obj_sense='maximize')
553-
ub[i] = sol_vmax.objective_value
567+
constr = kwargs[CONSTRAINTS].copy()
568+
if ax_type[0] == 'rate':
569+
constr += [[axes[0][0],'=',x]]
570+
elif ax_type[0] == 'yield':
571+
constr += [[{**axes[0][0], **{k:-v*x for k,v in axes[0][1].items()}},'=',0]]
572+
if ax_type[1] == 'rate':
573+
sol_vmin = fba(model,constraints=constr,obj=axes[1][0],obj_sense='minimize')
574+
lb[i] = ceil_dec(sol_vmin.objective_value,9)
575+
sol_vmax = fba(model,constraints=constr,obj=axes[1][0],obj_sense='maximize')
576+
ub[i] = floor_dec(sol_vmax.objective_value,9)
577+
elif ax_type[1] == 'yield':
578+
sol_vmin = yopt(model,constraints=constr,obj_num=axes[1][0],obj_den=axes[1][1],obj_sense='minimize')
579+
lb[i] = ceil_dec(sol_vmin.objective_value,9)
580+
sol_vmax = yopt(model,constraints=constr,obj_num=axes[1][0],obj_den=axes[1][1],obj_sense='maximize')
581+
ub[i] = floor_dec(sol_vmax.objective_value,9)
554582

555583
if num_axes == 2:
556584
x = [v for v in x_space] + [v for v in reversed(x_space)]
@@ -559,6 +587,7 @@ def phase_plane(model, axes, **kwargs):
559587
x.extend([x_space[0], x_space[0]])
560588
y.extend([lb[0], ub[0]])
561589
plot1 = plt.fill(x, y)
590+
# plot1 = plt.plot(x, y)
562591
plot1[0].axes.set_xlabel(ax_name[0])
563592
plot1[0].axes.set_ylabel(ax_name[1])
564593
plot1[0].axes.set_xlim(ax_limits[0][0]*1.05,ax_limits[0][1]*1.05)
@@ -570,22 +599,87 @@ def phase_plane(model, axes, **kwargs):
570599
elif num_axes == 3:
571600
max_diff_y = max([abs(l-u) for l,u in zip(lb,ub)])
572601
datapoints = []
573-
for x,l,u in zip(x_space,lb,ub):
602+
datapoints_top = []
603+
datapoints_bottom = []
604+
for i,(x,l,u) in enumerate(zip(x_space,lb,ub)):
574605
if l-u != 0:
575606
y_space = linspace(l,u,int(-(-points // (max_diff_y/abs(l-u)))))
576607
else:
577608
y_space = [0.0]
578-
for y in y_space:
579-
constr = [[axes[0],'=',x], [axes[1],'=',y]]
580-
sol_vmin = fba(model,constraints=constr,obj=axes[2],obj_sense='minimize')
581-
datapoints += [[x,y,sol_vmin.objective_value]]
582-
sol_vmax = fba(model,constraints=constr,obj=axes[2],obj_sense='maximize')
583-
datapoints += [[x,y,sol_vmax.objective_value]]
584-
hull = ConvexHull(datapoints)
609+
datapoints_top += [[]]
610+
datapoints_bottom += [[]]
611+
for j,y in enumerate(y_space):
612+
constr = kwargs[CONSTRAINTS].copy()
613+
if ax_type[0] == 'rate':
614+
constr += [[axes[0][0],'=',x]]
615+
elif ax_type[0] == 'yield':
616+
constr += [[{**axes[0][0], **{k:-v*x for k,v in axes[0][1].items()}},'=',0]]
617+
if ax_type[1] == 'rate':
618+
constr += [[axes[1][0],'=',y]]
619+
elif ax_type[1] == 'yield':
620+
constr += [[{**axes[1][0], **{k:-v*y for k,v in axes[1][1].items()}},'=',0]]
621+
if ax_type[2] == 'rate':
622+
sol_vmin = fba(model,constraints=constr,obj=axes[2][0],obj_sense='minimize')
623+
sol_vmax = fba(model,constraints=constr,obj=axes[2][0],obj_sense='maximize')
624+
elif ax_type[2] == 'yield':
625+
sol_vmin = yopt(model,constraints=constr,obj_num=axes[2][0],obj_den=axes[2][1],obj_sense='minimize')
626+
sol_vmax = yopt(model,constraints=constr,obj_num=axes[2][0],obj_den=axes[2][1],obj_sense='maximize')
627+
datapoints_top[i] += [len(datapoints)]
628+
datapoints += [[x,y,floor_dec(sol_vmax.objective_value,9)]]
629+
datapoints_bottom[i] += [len(datapoints)]
630+
datapoints += [[x,y,ceil_dec(sol_vmin.objective_value,9)]]
631+
632+
# Construct Denaunay triangles for plotting from all 6 perspectives
633+
triang = []
634+
# triangles top
635+
for i in range(len(datapoints_top)-1):
636+
temp_points = datapoints_top[i]+datapoints_top[i+1]
637+
pts = [[datapoints[idx_p][0],datapoints[idx_p][1]] for idx_p in temp_points]
638+
if matrix_rank(pts) > 1:
639+
triang_temp = Delaunay(pts).simplices
640+
triang += [[temp_points[idx] for idx in p] for p in triang_temp]
641+
# triangles bottom
642+
for i in range(len(datapoints_bottom)-1):
643+
temp_points = datapoints_bottom[i]+datapoints_bottom[i+1]
644+
pts = [[datapoints[idx_p][0],datapoints[idx_p][1]] for idx_p in temp_points]
645+
if matrix_rank(pts) > 1:
646+
triang_temp = Delaunay(pts).simplices
647+
triang += [[temp_points[idx] for idx in flip(p)] for p in triang_temp]
648+
# triangles front
649+
for i in range(len(x_space)-1):
650+
temp_points = [datapoints_top[i][0],datapoints_top[i+1][0],datapoints_bottom[i][0],datapoints_bottom[i+1][0]]
651+
pts = [[datapoints[idx_p][0],datapoints[idx_p][2]] for idx_p in temp_points]
652+
if matrix_rank(pts) > 1:
653+
triang_temp = Delaunay(pts).simplices
654+
triang += [[temp_points[idx] for idx in flip(p)] for p in triang_temp]
655+
# triangles back
656+
for i in range(len(x_space)-1):
657+
temp_points = [datapoints_top[i][-1],datapoints_top[i+1][-1],datapoints_bottom[i][-1],datapoints_bottom[i+1][-1]]
658+
pts = [[datapoints[idx_p][0],datapoints[idx_p][2]] for idx_p in temp_points]
659+
if matrix_rank(pts) > 1:
660+
triang_temp = Delaunay(pts).simplices
661+
triang += [[temp_points[idx] for idx in flip(p)] for p in triang_temp]
662+
# triangles left
663+
for i in range(len(datapoints_top[0])-1):
664+
temp_points = [datapoints_top[0][i],datapoints_top[0][i+1],datapoints_bottom[0][i],datapoints_bottom[0][i+1]]
665+
pts = [[datapoints[idx_p][1],datapoints[idx_p][2]] for idx_p in temp_points]
666+
if matrix_rank(pts) > 1:
667+
triang_temp = Delaunay(pts).simplices
668+
triang += [[temp_points[idx] for idx in p] for p in triang_temp]
669+
# triangles right
670+
for i in range(len(datapoints_top[-1])-1):
671+
temp_points = [datapoints_top[-1][i],datapoints_top[-1][i+1],datapoints_bottom[-1][i],datapoints_bottom[-1][i+1]]
672+
pts = [[datapoints[idx_p][1],datapoints[idx_p][2]] for idx_p in temp_points]
673+
if matrix_rank(pts) > 1:
674+
triang_temp = Delaunay(pts).simplices
675+
triang += [[temp_points[idx] for idx in p] for p in triang_temp]
676+
677+
# hull = ConvexHull(datapoints)
585678
x = [d[0] for d in datapoints]
586679
y = [d[1] for d in datapoints]
587680
z = [d[2] for d in datapoints]
588-
ax = a3.Axes3D(plt.figure())
681+
# ax = a3.Axes3D(plt.figure())
682+
ax = plt.figure().add_subplot(projection='3d')
589683
ax.dist=10
590684
ax.azim=30
591685
ax.elev=10
@@ -595,112 +689,27 @@ def phase_plane(model, axes, **kwargs):
595689
ax.set_xlabel(ax_name[0])
596690
ax.set_ylabel(ax_name[1])
597691
ax.set_zlabel(ax_name[2])
598-
plot1 = ax.plot_trisurf(x,y,z,triangles=hull.simplices,linewidth=0.2, antialiased=True, alpha=0.67)
692+
# set higher color value for 'larger' values in all dimensions
693+
# use middle value of triangles instead of means and then norm
694+
colors = [nan for _ in triang]
695+
for i,t in enumerate(triang):
696+
p = [datapoints[i] for i in t]
697+
interior_point = [0.0, 0.0, 0.0]
698+
for d in range(3):
699+
v = [q[d] for q in p]
700+
interior_point[d] = mean([max(v),min(v)])/max(abs(array(ax_limits[d])))
701+
colors[i] = sum(interior_point) # norm(interior_point)
702+
lw = min([1,6.0/len(triang)])
703+
plot1 = ax.plot_trisurf(x,y,z,triangles=triang,linewidth=lw,edgecolors='black', antialiased=True, alpha=0.90) # array=colors, cmap=plt.cm.winter
704+
colors = colors/max(colors)
705+
colors = get_cmap("Spectral")(colors)
706+
plot1.set_fc(colors)
599707
if show:
600708
plt.show()
601709
return plot1
602-
603-
def yield_space(model, axes, **kwargs):
604-
reaction_ids = model.reactions.list_attr("id")
605-
606-
if CONSTRAINTS in kwargs:
607-
kwargs[CONSTRAINTS] = parse_constraints(kwargs[CONSTRAINTS],reaction_ids)
608-
else:
609-
kwargs[CONSTRAINTS] = None
610-
611-
if SOLVER not in kwargs:
612-
kwargs[SOLVER] = None
613-
solver = select_solver(kwargs[SOLVER],model)
614-
615-
if 'show' in kwargs:
616-
show = kwargs['show']
617-
else:
618-
show = True
619-
620-
axes = [list(ax) for ax in axes] # cast to list of lists
621-
num_axes = len(axes)
622-
if num_axes not in [2,3]:
623-
raise Exception('Please define 2 or 3 axes as a list of tuples [ax1, ax2, (optional) ax3] with ax1 = (den,num).\n'+\
624-
'"den" and "num" being linear expressions.')
625-
626-
if 'points' in kwargs:
627-
points = kwargs['points']
628-
else:
629-
if num_axes == 2:
630-
points = 40
631-
else:
632-
points = 25
633-
634-
ax_name = ["" for _ in range(num_axes)]
635-
ax_limits = [(nan,nan) for _ in range(num_axes)]
636-
for i,ax in enumerate(axes):
637-
ax[0] = parse_linexpr(ax[0],reaction_ids)[0]
638-
ax[1] = parse_linexpr(ax[1],reaction_ids)[0]
639-
ax_name[i] = '('+linexprdict2str(ax[0])+') / ('+linexprdict2str(ax[1])+')'
640-
sol_min = yopt(model,obj_num=ax[0],obj_den=ax[1],constraints=kwargs[CONSTRAINTS],solver=solver,obj_sense='minimize')
641-
sol_max = yopt(model,obj_num=ax[0],obj_den=ax[1],constraints=kwargs[CONSTRAINTS],solver=solver,obj_sense='maximize')
642-
# abort if any of the yields are unbounded or undefined
643-
unbnd = [i+1 for i,v in enumerate([sol_min,sol_max]) if v.status == UNBOUNDED]
644-
if any(unbnd):
645-
raise Exception('One of the specified yields is unbounded or undefined. Yield space cannot be generated.')
646-
ax_limits[i] = [min((0,sol_min.objective_value)),max((0,sol_max.objective_value))]
647-
648-
# compute points
649-
x_space = linspace(ax_limits[0][0], ax_limits[0][1], num=points)
650-
lb = full(points, nan)
651-
ub = full(points, nan)
652-
for i,x in enumerate(x_space):
653-
constr = [{**axes[0][0], **{k:-v*x for k,v in axes[0][1].items()}},'=',0]
654-
sol_vmin = yopt(model,constraints=constr,obj_num=axes[1][0],obj_den=axes[1][1],obj_sense='minimize')
655-
lb[i] = sol_vmin.objective_value
656-
sol_vmax = yopt(model,constraints=constr,obj_num=axes[1][0],obj_den=axes[1][1],obj_sense='maximize')
657-
ub[i] = sol_vmax.objective_value
658710

659-
if num_axes == 2:
660-
x = [v for v in x_space] + [v for v in reversed(x_space)]
661-
y = [v for v in lb] + [v for v in reversed(ub)]
662-
if lb[0] != ub[0]:
663-
x.extend([x_space[0], x_space[0]])
664-
y.extend([lb[0], ub[0]])
665-
plot1 = plt.fill(x, y)
666-
plot1[0].axes.set_xlabel(ax_name[0])
667-
plot1[0].axes.set_ylabel(ax_name[1])
668-
plot1[0].axes.set_xlim(ax_limits[0][0]*1.05,ax_limits[0][1]*1.05)
669-
plot1[0].axes.set_ylim(ax_limits[1][0]*1.05,ax_limits[1][1]*1.05)
670-
if show:
671-
plt.show()
672-
return plot1
711+
def ceil_dec(v,n):
712+
return ceil(v*(10**n))/(10**n)
673713

674-
elif num_axes == 3:
675-
max_diff_y = max([abs(l-u) for l,u in zip(lb,ub)])
676-
datapoints = []
677-
for x,l,u in zip(x_space,lb,ub):
678-
if l-u != 0:
679-
y_space = linspace(l,u,int(-(-points // (max_diff_y/abs(l-u)))))
680-
else:
681-
y_space = [0.0]
682-
for y in y_space:
683-
constr = [[{**axes[0][0], **{k:-v*x for k,v in axes[0][1].items()}},'=',0],\
684-
[{**axes[1][0], **{k:-v*y for k,v in axes[1][1].items()}},'=',0]]
685-
sol_vmin = yopt(model,constraints=constr,obj_num=axes[2][0],obj_den=axes[2][1],obj_sense='minimize')
686-
datapoints += [[x,y,sol_vmin.objective_value]]
687-
sol_vmax = yopt(model,constraints=constr,obj_num=axes[2][0],obj_den=axes[2][1],obj_sense='maximize')
688-
datapoints += [[x,y,sol_vmax.objective_value]]
689-
hull = ConvexHull(datapoints)
690-
x = [d[0] for d in datapoints]
691-
y = [d[1] for d in datapoints]
692-
z = [d[2] for d in datapoints]
693-
ax = a3.Axes3D(plt.figure())
694-
ax.dist=10
695-
ax.azim=30
696-
ax.elev=10
697-
ax.set_xlim(ax_limits[0])
698-
ax.set_ylim(ax_limits[1])
699-
ax.set_zlim(ax_limits[2])
700-
ax.set_xlabel(ax_name[0])
701-
ax.set_ylabel(ax_name[1])
702-
ax.set_zlabel(ax_name[2])
703-
plot1 = ax.plot_trisurf(x,y,z,triangles=hull.simplices,linewidth=0.2, antialiased=True, alpha=0.67)
704-
if show:
705-
plt.show()
706-
return plot1
714+
def floor_dec(v,n):
715+
return floor(v*(10**n))/(10**n)

0 commit comments

Comments
 (0)