Commit 74b8d8bb authored by SOLIMAN Sylvain's avatar SOLIMAN Sylvain

Merge branch 'release/v4.2.0'

parents a7381947 05835b48
......@@ -20,5 +20,7 @@ indent_style = space
indent_size = 4
# Tab indentation (no size specified)
[Makefile]
[Makefile, **.{js,json}]
indent_style = tab
......@@ -6,16 +6,12 @@ platform/current
modules/graphviz/graphviz_swiprolog
modules/partialfrac/roots
modules/sbml/sbml_swiprolog
modules/glucose/glucose-syrup
modules/glucose/glucose_swiprolog
swipl-biocham
Untitled*.ipynb
.ipynb_checkpoints
devdoc/
doc/
junit.xml
biocham_kernel/commands.py
biocham_kernel/commands.js
biocham-src-???????.zip
web/biocham.zip
library/cmaes.c
......@@ -24,3 +20,19 @@ library/cmaes_interface.h
**/actparcmaes.par
**/allcmaes.dat
**/errcmaes.err
biocham_jupyter/kernel/biocham_kernel/commands.py
biocham_jupyter/kernel/biocham_kernel/commands.js
biocham_jupyter/kernel/biocham_kernel.egg-info/
biocham_jupyter/kernel/build/
biocham_jupyter/kernel/dist/
biocham_jupyter/guinbextension/out/
biocham_jupyter/guinbextension/config/workflows/commands.py
biocham_jupyter/guinbextension/config/workflows/workflows.js
biocham_jupyter/guinbextension/gui-build/
biocham_jupyter/guinbextension/node_modules/
biocham_jupyter/guinbextension/package-lock.json
.jupyter/
*.js~
*.py~
*.pyc
.DS_Store
......@@ -8,29 +8,41 @@ stages:
- release
- deploy
build:
linux build:
stage: build
tags:
- linux
script:
- make biocham jupyter
- make biocham install_kernel
cache:
paths:
- swipl-biocham
- biocham
- swipl-biocham
- library/*.o
- library/cmaes*.*
artifacts:
paths:
- modules/glucose/glucose-syrup
- modules/glucose/patch.done
test:
linux test:
stage: test
tags:
- linux
script:
- make test
# Gitlab pages served on https://lifeware.gitlabpages.inria.fr/biocham
pages:
stage: release
tags:
- linux
script:
- make doc/index.html web/biocham.zip
- mkdir -p public
- cp -r doc devdoc web/* public
- cp -r doc devdoc web/*.{html,png,zip} public
- if [[ -n "$CI_COMMIT_TAG" ]] ; then cp public/biocham.zip public/biocham_$CI_COMMIT_TAG.zip ; mv public/doc public/doc_$CI_COMMIT_TAG ; ln -s doc_$CI_COMMIT_TAG public/doc ; fi
- cp public/biocham.zip public/biocham_latest.zip
artifacts:
paths:
- public
......@@ -38,22 +50,24 @@ pages:
only:
- tags
- web
except:
- /^(?!master).+@/
# except:
# - /^(?!master).+@/
docker:
stage: deploy
tags:
- linux
script:
- if [[ -n "$(docker ps -qa)" ]] ; then docker stop $(docker ps -qa) ; docker rm $(docker ps -qa) ; fi
- if [[ -n "$(docker images -f "dangling=true" -q)" ]] ; then docker rmi $(docker images -f "dangling=true" -q) ; fi
- if [[ -n "$(docker images -q)" ]] ; then docker rmi $(docker images -q) ; fi
- export IMAGE_TAG=$CI_REGISTRY_IMAGE${CI_COMMIT_TAG:+":$CI_COMMIT_TAG"}
- docker build --no-cache -t $IMAGE_TAG tmpnb/lifeware_biocham/
- docker build --no-cache -t $IMAGE_TAG --build-arg tag=${CI_COMMIT_TAG:-latest} tmpnb/lifeware_biocham/
- docker login -u $CI_REGISTRY_USER -p $CI_JOB_TOKEN $CI_REGISTRY
- docker push $IMAGE_TAG
- docker rmi $IMAGE_TAG
only:
- tags
- web
except:
- /^(?!master).+@/
# except:
# - /^(?!master).+@/
# make jupyter will make biocham without checking unitary tests (avoiding potential problems of access to BioModels in sbml.plt)
ADDITIONAL_MODULES= \
modules/sbml/sbml_utils.pl \
modules/partialfrac/partialfraction.pl \
modules/glucose/glucose.pl
modules/partialfrac/partialfraction.pl
MODULES=$(shell sed -n -E 's/^[+-] (.*\.pl)$$/\1/p' toc.org) \
$(ADDITIONAL_MODULES)
# load_test_files/1 should make this useless, but I cannot find how to use it
......@@ -11,25 +12,30 @@ ifndef $(PREFIX)
PREFIX=/usr/local
endif
$(foreach var, CC PLBASE PLARCH PLLIB PLCFLAGS PLLDFLAGS PLSOEXT, \
$(foreach var, CC PLBASE PLARCH PLLIB PLCFLAGS PLLDFLAGS PLSOEXT PLVERSION, \
$(eval \
$(shell \
swipl -dump-runtime-variables | \
swipl --dump-runtime-variables | \
grep ^$(var)= | \
sed -E 's/^/export /;s/="/=/;s/";$$//')))
# from version 7.7.13 default toplevel is 'halt' if there are initialization
# goals
SWIPL_GE_7713=$(shell [ $(PLVERSION) -ge 70713 ] && echo true)
TOPLEVEL=
ifeq ($(SWIPL_GE_7713),true)
TOPLEVEL=--toplevel=prolog
endif
SUBDIRS=$(dir $(wildcard modules/*/Makefile))
# glucose source needs its own header files
INCLUDEDIRS=$(PLBASE)/include $(SUBDIRS) \
modules/glucose/glucose-syrup modules/glucose/glucose-syrup/core
INCLUDEDIRS=$(PLBASE)/include $(SUBDIRS)
RPATH=
ifneq ($(LD_RUN_PATH),)
RPATH=-Wl,-rpath=$(LD_RUN_PATH)
endif
CFLAGS=$(addprefix -I, $(INCLUDEDIRS)) $(PLCFLAGS)
CXXFLAGS=$(CFLAGS) -Wno-logical-op-parentheses `pkg-config --cflags libgvc`
CXXFLAGS=$(CFLAGS) -std=c++11 `pkg-config --cflags libgvc`
LDFLAGS=$(PLLDFLAGS) $(RPATH) $(addprefix -L, $(PLBASE)/lib/$(PLARCH)/)
......@@ -39,27 +45,34 @@ ifeq ($(strip $(LIBSBML)),)
endif
LDLIBS=$(PLLIB) `pkg-config --libs libgvc` $(LIBSBML) -lgsl -lgslcblas -lm
SWIPL=$(PWD)/swipl-biocham
SWIPL=$(CURDIR)/swipl-biocham
CMAES_LIB=library/cmaes.c library/cmaes.h library/cmaes_interface.h
all: biocham biocham_debug test doc/index.html jupyter
JUPYTER_DIR=$(CURDIR)/biocham_jupyter
KERNEL_DIR=$(JUPYTER_DIR)/kernel/biocham_kernel
WORKFLOWS_DIR=$(JUPYTER_DIR)/guinbextension/src/config/workflows
# NOTEBOOKS=$(shell find . -type f -name '*.ipynb' -print)
NOTEBOOKS=library/examples/doctor_in_the_cell/diagnosis.ipynb library/examples/tutorial/tutorial17cmsb.ipynb
REFDIR=nbhtml
all: biocham biocham_debug test doc/index.html pldoc install_kernel install_gui
quick: unit_tests
.PHONY: all slow test unit_tests clean web cabernet cabernet_restart \
jupyter gadagne gadagne_restart jupyter_tests install pldoc
install_kernel gadagne gadagne_restart jupyter_tests install pldoc devdoc \
refs
# should we depend on jupyter target?
install: biocham biocham_debug
mkdir -p $(PREFIX)/bin
mkdir -p $(PREFIX)/share/biocham
cp $? $(PREFIX)/share/biocham/
cp $^ $(PREFIX)/share/biocham/
ln -fs $(PREFIX)/share/biocham/biocham $(PREFIX)/bin/biocham
ln -fs $(PREFIX)/share/biocham/biocham_debug $(PREFIX)/bin/biocham_debug
cp -r library $(PREFIX)/share/biocham/
cp -r doc $(PREFIX)/share/biocham/
cp -r biocham_kernel $(PREFIX)/share/biocham/
biocham: $(SWIPL) $(MODULES) toc.org $(CMAES_LIB) \
library/gsl_solver.o library/cmaes.o library/csv_reader.o Makefile
......@@ -74,29 +87,24 @@ biocham_debug: $(SWIPL) $(MODULES) $(TEST_MODULES) toc.org $(CMAES_LIB) \
$(SWIPL) -q -o biocham_debug \
--goal="\
set_prolog_flag(verbose, normal), \
initialize" --stand_alone=true -c $(MODULES) $(TEST_MODULES)
initialize" \
$(TOPLEVEL) --stand_alone=true -c $(MODULES) $(TEST_MODULES)
$(SWIPL): $(SWIPL).c \
modules/graphviz/graphviz_swiprolog.c \
modules/sbml/sbml_swiprolog.c \
modules/partialfrac/roots.c \
modules/glucose/glucose_swiprolog.cc \
modules/glucose/glucose-syrup/core/Solver.cc
modules/partialfrac/roots.c
for dir in $(SUBDIRS) ; do $(MAKE) -C $$dir ; done
$(CXX) $(CXXFLAGS) $^ $(LDFLAGS) $(LDLIBS) -lstdc++ -o $@
# patch line is to remove output of noisy comment on stdout
modules/glucose/glucose-syrup/core/Solver.cc: modules/glucose/Solver.patch
curl -fsSLO http://www.labri.fr/perso/lsimon/downloads/softwares/glucose-syrup.tgz
tar -C $(dir $<) -xf glucose-syrup.tgz
rm -f glucose-syrup.tgz
patch $@ < $<
doc/index.html doc/commands.js: biocham pldoc
doc/index.html doc/commands.js: biocham
./biocham --generate-doc
pldoc:
echo "doc_save(., [if(true), doc_root('doc/pldoc')])." | swipl -q
echo "doc_save(., [if(true), doc_root('doc/pldoc')])." | swipl -q > /dev/null
devdoc:
./biocham --generate-devdoc
# quick ones only
unit_tests: biocham_debug
......@@ -106,33 +114,43 @@ unit_tests: biocham_debug
test: biocham_debug jupyter_tests
echo "flag(slow_test, _, true), run_tests_and_halt." | ./biocham_debug
jupyter_tests: jupyter
PYTHONPATH=$(PWD) jupyter nbconvert --execute --stdout library/examples/cmsb_2017/sigmoids.ipynb > /dev/null
jupyter_tests: install_kernel
PATH=$(CURDIR):$(PATH) jupyter nbconvert --execute --stdout library/examples/cmsb_2017/sigmoids.ipynb > /dev/null
# runs test unit %
test_%: biocham_debug
echo "flag(slow_test, _, true), run_tests_and_halt('$*')." | ./biocham_debug
jupyter: biocham biocham_kernel/commands.js biocham_kernel/commands.py
- jupyter kernelspec install --user --name=biocham biocham_kernel
- jupyter nbextension install --user biocham_gui/menu
- jupyter nbextension enable --user menu/main
- jupyter nbextension disable biocham_gui/dist/bundle
- jupyter nbextension disable biocham_gui/main
- jupyter nbextension disable gui_toolbar/main
install_kernel: biocham $(KERNEL_DIR)/commands.js $(KERNEL_DIR)/commands.py $(WORKFLOWS_DIR)/workflows.js
- pip3 install --user $(JUPYTER_DIR)/kernel
- jupyter kernelspec install --user --name=biocham $(KERNEL_DIR)
install_gui: $(WORKFLOWS_DIR)/workflows.js
- cd $(JUPYTER_DIR)/guinbextension && npm run build && npm run docs:build
- jupyter nbextension install --user $(JUPYTER_DIR)/guinbextension/gui-build/
- jupyter nbextension enable --user gui-build/nbextension
install_gui_dev: $(WORKFLOWS_DIR)/workflows.js
- cd $(JUPYTER_DIR)/guinbextension && npm run dev && npm run docs:build
- jupyter nbextension install --user $(JUPYTER_DIR)/guinbextension/gui-build/
- jupyter nbextension enable --user gui-build/nbextension
biocham_kernel/commands.py: biocham
$(KERNEL_DIR)/commands.py: biocham
rm -f $@
echo '# Auto generated file, please do not edit manually' > $@
echo 'commands = [' >> $@
./biocham --list-commands | tail -n +4 | sort -u | sed -e 's/^\(.*\)$$/ "\1",/' >> $@
echo ']' >> $@
cp $@ $(WORKFLOWS_DIR)
biocham_kernel/commands.js: doc/commands.js
$(KERNEL_DIR)/commands.js: doc/commands.js
cp -f $< $@
# only static .bc files in that dir
$(WORKFLOWS_DIR)/workflows.js:
# for file in $(WORKFLOWS_DIR)/nb/*.ipynb ; do jupyter nbconvert --to script $$file ; done
python3 $(WORKFLOWS_DIR)/make_workflows.py -i $(WORKFLOWS_DIR)/nb/ -o $@
clean:
- for dir in $(SUBDIRS) ; do $(MAKE) -C $$dir clean ; done
- rm biocham
......@@ -156,8 +174,10 @@ modules/c-cmaes/src:
git clone https://github.com/CMA-ES/c-cmaes.git modules/c-cmaes ; \
fi
# size issue, so we exclude the video
web/biocham.zip:
git archive --prefix=biocham/ -o $@ HEAD
-zip $@ -d biocham/web/biocham4.mov biocham/web/\*.zip
web: web/biocham.zip web/index.html web/logo.png doc/index.html
-rsync -avz $^ doc lifeware:/nfs/web-epi/biocham4/
......@@ -180,8 +200,20 @@ gadagne_restart: gadagne
release:
@read -p "Version number (e.g. 4.1.0): " version && \
sed -i'' -e "s/^version('.*')/version('$$version')/" about.pl && \
sed -i'' -e "s+^<h1>version.*</h1>+<h1>version $$version $(shell date '+%b %Y')</h1>+" web/index.html
@read -p "We will now commit tag and push the new version [press any key]" dummy
git commit about.pl web/index.html -m "Version $$version"
git tag -a v$$version -m "Version $$version"
LC_ALL=C sed -i'' -e "s+^<h1>version.*</h1>+<h1>version $$version $(shell date '+%B %Y')</h1>+" web/index.html && \
git commit about.pl web/index.html -m "Version $$version" && \
git tag -a v$$version -m "Version $$version" && \
git push --tags
refs: $(NOTEBOOKS) biocham
mkdir -p $(REFDIR)
jupyter nbconvert --ExecutePreprocessor.timeout=None \
--to html --output-dir $(REFDIR) --execute $(NOTEBOOKS)
for file in $(NOTEBOOKS) ; do \
out=$(REFDIR)/$$(basename $${file%%ipynb}html) && \
if [ -f $$out.ref ] ; then \
diff -q $$out $$out.ref || false ; break ; \
else \
cp $$out $$out.ref ; \
fi ; \
done
The file toc.org (edit with tab) is used for three things
- for creating the Biocham runtime from the source code,
by defining the order in which the source files have to be loaded;
- for generating the Biocham documentation in the directories doc and devdoc
(from annotations in the source code using the runtime for running the examples),
by defining the order of the sections and their title;
- for generating the Biocham notebook and web interface
with the same structure as the documentation.
The Makefile does the jobs.
Documentation
-------------
La documentation est générée automatiquement
à partir du code et du fichier toc.org (ouverture des sous sections avec tab)
dans les repertoireq doc/index.html et devdoc/index.html
(versions index.tex également disponibles pour xelatex)
Le fichier toc.org definit également l'ordre de chargement des fichiers Prolog
pour la compilation de Biocham, ce qui contraint donc cet ordre à ne pas
faire de référence en avant, tout doit être défini avant y compris les opérateurs.
Il est bon de documenter les prédicats
avant même de les programmer complètement.
Par exemple dans le fichier odefunction.pl:
- on définit un module pour la commande add_function qui est exportée du module.
- le but devdoc crée une nouvelle section dans la documentation
(l’ordre de chargement des modules est déterminé dans le fichier
détermine l’ordre des sections dans la doc
- on définit le prédicat add_function en disant
add_function(FunctionList) :-
- qu’il a un nombre variable d’arguments traités en liste
biocham_command(*),
type(FunctionList, '*'(term = term)),
- que ce prédicat se retrouvera documenté et donn& avec un exemple (exécuté et inséré dans la doc par make)
doc('
adds reactions to compute the result of a function of the current variables in the concentration of a new variable at steady state.
\\begin{example}
'),
biocham_silent(clear_model),
biocham(present(x,1)),
biocham(present(y,3)),
biocham(add_function(z=x+y)),
biocham(list_reactions),
biocham(list_ode),
doc('
\\end{example}
‘),
- puis vient l’implémentation en Prolog
new_ode_system(OdeSystem),
export_ode(FunctionList, OdeSystem),
import_reactions_from_ode_system(OdeSystem),
delete_item(OdeSystem).
Par ailleurs tous les fichiers plt sont exécutés comme des programmes de test.
C’est bien aussi de les écrire pour tester l’implémentation
et de les cometre pour tester les upgrade futurs.
Par exemple odefunction.plt
# Biocham
This README file is mostly aimed at people *contributing* to Biocham
## Getting Started
After `git clone` you might need to run once the [install.sh](./install.sh)
script in order to install all build dependencies and to try to compile the
project.
After that `make` will build the whole project, including documentation,
tests, debug version, etc., which might be quite slow.
You might want to use `make biocham`, `make biocham_debug` (a SWI-Prolog
toplevel with all biocham files loaded) or `make test_<unit>` (to run a single
test unit) for faster feedback.
Note that `make test` will give you, if available, some test coverage
estimation, and that `make test_<unit>` will attempt to get into more details
about which clauses were not covered (but this highly depends on the SWIPL
version).
## Documentation
The file [toc.org](./toc.org) is used for three things:
- for creating the Biocham runtime from the source code,
by defining the order in which the source files have to be loaded;
- for generating the Biocham documentation in the directories [doc](./doc/)
and [devdoc](./devdoc/)
(from annotations in the source code using the runtime for running the examples),
by defining the order of the sections and their title;
- for generating the Biocham notebook and web interface
with the same structure as the documentation.
It is also recommended to use
[PlDoc](https://www.swi-prolog.org/pldoc/doc_for?object=section(%27packages/pldoc.html%27)) comments, the resulting documentation is
in [doc/pldoc](./doc/pldoc/).
## Documentation example
In the [odefunction.pl](./odefunction.pl) file:
- a module is defined for the `add_function` command that is then exported;
- the `devdoc` goal creates a new section in the developer's documentation (in
the loading order fixed by `toc.org`)
- the predicate `add_function` is then defined:
```prolog
add_function(FunctionList) :-
% the biocham command has a variable number of arguments treated like a list
biocham_command(*),
type(FunctionList, '*'(term = term)),
% it will appear in the `doc/index.html` file with an example that will be
% executed and inserted
doc('
adds reactions to compute the result of a function of the current variables in the concentration of a new variable at steady state.
\\begin{example}
'),
biocham_silent(clear_model),
biocham(present(x,1)),
biocham(present(y,3)),
biocham(add_function(z=x+y)),
biocham(list_reactions),
biocham(list_ode),
doc('
\\end{example}
),
% now comes the proper Prolog implementation
new_ode_system(OdeSystem),
export_ode(FunctionList, OdeSystem),
import_reactions_from_ode_system(OdeSystem),
delete_item(OdeSystem).
```
## Tests
All `.plt` files are executed as test programs. See for instance
[odefunction.plt](./odefunction.plt).
## General stuff
There is an [EditorConfig](https://editorconfig.org) file that specifies
indentation settings and such: [.editorconfig](./.editorconfig), please make
your editor aware of it.
It might be a good idea to follow [Prolog programming
guidelines](https://lifeware.inria.fr/~soliman/post/prolog_guidelines/).
We tend to use GitFlow as our branching model, see [our internal
Wiki](https://lifeware.inria.fr/wiki/Internal/WorkingOnBiocham) for more
details.
......@@ -8,10 +8,10 @@
about/0
]).
version('4.1.16').
version('4.2.0').
copyright(
'Copyright (C) 2003-2018 Inria, EPI Lifeware, Saclay-Île de France, France'
'Copyright (C) 2003-2019 Inria, EPI Lifeware, Saclay-Île de France, France'
).
license('GNU GPL 2').
......
......@@ -703,16 +703,22 @@ always_positive(A) :-
!,
A >= 0.
always_positive(A):- % assumed for concentrations, parameters
always_positive(A):- % assumed for concentrations, parameters
atom(A),
!.
always_positive(- A) :-
always_negative(A).
always_positive(_A ^ B) :-
always_positive(A ^ B) :-
number(B),
0 is B mod 2.
(
0 is B mod 2
->
true
;
always_positive(A)
).
always_positive(A + B) :-
always_positive(A),
......
......@@ -25,44 +25,51 @@ The Biochemical Abstract Machine (Biocham) is a modelling software for cell syst
software.').
:- doc('
This reference manual (as its extended version for developpers) is automaticaly generated from the source code of Biocham.').
This reference manual (as its extended version for developers) is automatically generated from the source code of Biocham.').
:- doc('
Biocham v4 is mainly composed of :
\\begin{itemize}
\\item
a rule-based language for modeling biochemical processes with reactions and/or influences (respectively SBML2 and SBML3-qual compatible),
\\item
a hierarchy of semantics (differential, stochastic, Petri Net, Boolean) to interpret such models,
a rule-based language for modeling biochemical reaction networks and influence networks
\\begin{itemize}
\\item compatible with SBML2 \\doi{10.1038/npre.2008.2715.1} and SBML3-qual \\doi{10.1186/1752-0509-7-135} respectively
\\item interpreted in a hierarchy of semantics (differential, stochastic, Petri Net, Boolean)
\\end{itemize}
\\item
a temporal logic based language to formalize the temporal behaviors of biochemical networks,
a temporal logic based language to formalize possibly imprecise behaviours both qualitative and quantitative \\cite{RBFS11tcs}}
\\item
several unique features for developing/analyzing/correcting/completing/calibrating/coupling/synthesizing reaction and influence networks with respect to formal specifications of their behaviour.
\\end{itemize}
Biocham v4 is a complete rewriting of Biocham v3.
Biocham v4 is a complete rewriting (in SWI-Prolog) of Biocham v3 (written in GNU-Prolog).
It introduces some new features, mainly:
\\begin{itemize}
\\item influence network models,
\\item quantitative temporal logic patterns,
\\item compilation of functions in reaction networks,
\\item learning models from data.
\\item influence network models \\cite{FMRS18tcbb}
\\item quantitative temporal logic patterns and trace simplifications \\cite{FS14book}
\\item compilation of mathematical functions and simple programs in reaction networks \\cite{FLBP17cmsb}
\\item PAC learning of influence models from data \\cite{CFS17cmsb}
\\item a notebook based on Jupyter
\\end{itemize}
Version 4.1 adds to 4.0:
plus since v4.1:
\\begin{itemize}
\\item multi-conditions (i.e. temporal logic constraints on a model with different parameter values or initial concentrations)
\\item bug fix in the computation of satisfaction degrees
\\item multistationarity check \\cite{BFS18jtb}}
\\item robustness optimization \\cite{FS18cmsb}
\\item detection of model reductions by constrained subgraph epimorphisms (SEPI) \\cite{GSF10bi}
\\item tropical equilibrations \\cite{SFR14amb}
\\item a menu of commands in the notebook
\\item a Graphical User Interface based on the notebook
\\end{itemize}
Some features of Biocham v3 are still not implemented in v4, mainly:
\\begin{itemize}
\\item the Graphical User Interface (only the notebook)
\\item the graphical editor (SBGN compatible)
\\item the sensivity and robustness measures
\\item the detection of model reductions by subgraph epimorphisms (SEPI)
\\item hybrid simulations
\\item hybrid stochastic-differential simulations \\cite{CFJS15tomacs}
\\end{itemize}
or less efficiently (because of on-the-fly C compilation instead of native Prolog code):
\\begin{itemize}
\\item numerical integration using GSL library is slower and may be less accurate
\\item parameter search, sensitivity and robustness measures are currently slower than Biocham v3.
\\end{itemize}
').
......@@ -88,6 +95,7 @@ initialize :-
set_counter(item_id, 0),
set_plot_driver(gnu_plot),
set_image_viewer_driver(open_file),
nb_setval(ode_viewer, inline),
set_draw_graph_driver(graph_pdf),
nb_setval(graph_pdf, 0),
nb_setval(current_models, []),
......
/*
The left-side dropdown submenu is adapted from the usual right-side code at
<https://github.com/ipython/ipython/blob/master/IPython/html/static/style/style.min.css#L10664>
*/
.dropdown-submenu-left > .dropdown-menu {
left: auto;
right: 100%;
}
/* For space-saving menus */
.dropdown-submenu > .dropdown-menu.dropdown-menu-compact {
left: 50%;
top: 100%;
}
.dropdown-submenu-left > .dropdown-menu.dropdown-menu-compact {
left: auto;
right: 50%;
}
.dropdown-submenu-left > a:after {
visibility: hidden;
}
.dropdown-submenu-left > a:before {
display: inline-block;
font: normal normal normal 14px/1 FontAwesome;
font-size: inherit;
text-rendering: auto;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
transform: translate(0, 0);