├── bonesis0 ├── __init__.py ├── clingo_solving.py ├── subset_portfolio.cfg ├── gil_utils.py ├── proxy_control.py ├── diversity.py └── asp_encoding.py ├── examples ├── toy1.sif ├── diversity.py ├── control_attractors.py ├── dedekind.py ├── view_configurations.py ├── contexts.py ├── spec_state_equalities.py ├── extra_somes.py ├── control_fixpoints.py ├── all_fixpoints.py ├── optimization.py ├── project_bns.py └── count_matching.py ├── .gitignore ├── MANIFEST.in ├── .github ├── release-drafter.yml └── workflows │ ├── tests.yml │ └── release.yml ├── tests ├── fork.py ├── test_domain.py ├── in_attractor.py ├── all_attractors.py ├── test_filters.py ├── mutant_all_attractors.py ├── allreach_attractors.py ├── allreach_fixpoints.py ├── hypercubes.py ├── test_utils.py ├── test_aeon.py ├── timeout_and_gil.py ├── timeout_and_gil_diverse.py ├── test_universal.py ├── test_configurations.py ├── test_view_extra.py ├── allreach_fixpoints_mutants.py ├── test_language.py ├── test_fixed.py ├── test_managers.py └── test_reachability.py ├── README.md ├── conda └── meta.yaml ├── pyproject.toml ├── bonesis ├── debug.py ├── snippets.py ├── utils.py ├── __init__.py ├── cli.py ├── manager.py ├── domains.py ├── reprogramming.py ├── aeon.py ├── language.py └── views.py ├── LICENSE.txt └── Licence_CeCILL_V2.1-fr.txt /bonesis0/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/toy1.sif: -------------------------------------------------------------------------------- 1 | A + C 2 | C + B 3 | B - C 4 | C + D 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.egg-info 2 | .ipynb* 3 | __pycache__ 4 | dist 5 | build 6 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | prune .github 2 | prune examples 3 | prune tests 4 | prune build 5 | prune conda 6 | -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name-template: "v$NEXT_MINOR_VERSION" 2 | tag-template: "v$NEXT_MINOR_VERSION" 3 | template: | 4 | $CHANGES 5 | -------------------------------------------------------------------------------- /tests/fork.py: -------------------------------------------------------------------------------- 1 | import bonesis 2 | 3 | dom = bonesis.InfluenceGraph.complete("abc") 4 | data = {"A": {"a": 1}} 5 | 6 | bo1 = bonesis.BoNesis(dom, data) 7 | print(id(bo1)) 8 | 9 | bo2 = bo1.fork() 10 | print(id(bo2)) 11 | print(id(bo2.manager.bo)) 12 | -------------------------------------------------------------------------------- /tests/test_domain.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import bonesis 4 | 5 | class TestInfluenceGraph(unittest.TestCase): 6 | def test_complete(self): 7 | dom = bonesis.InfluenceGraph.complete(3, 1, loops=False, exact=True) 8 | bo = bonesis.BoNesis(dom) 9 | bns = bo.boolean_networks() 10 | self.assertEqual(bns.count(), 8) 11 | -------------------------------------------------------------------------------- /examples/diversity.py: -------------------------------------------------------------------------------- 1 | 2 | import pandas as pd 3 | 4 | import bonesis 5 | bonesis.enable_debug() 6 | 7 | N = 20 8 | 9 | bonesis.settings["parallel"] = 1 10 | 11 | dom = bonesis.InfluenceGraph.complete("abc", 1) 12 | 13 | bo = bonesis.BoNesis(dom) 14 | 15 | print(pd.DataFrame(list(bo.boolean_networks(limit=N)))) 16 | print(pd.DataFrame(list(bo.diverse_boolean_networks(limit=N)))) 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BoNesis - Boolean Network synthesis 2 | 3 | Synthesis of Most Permissive Boolean Networks from network architecture and dynamical properties 4 | 5 | This software is distributed under the [CeCILL v2.1](http://www.cecill.info/index.en.html) free software license (GNU GPL compatible). 6 | 7 | For installation instructions and usage, see [documentation](https://bnediction.github.io/bonesis). 8 | -------------------------------------------------------------------------------- /tests/in_attractor.py: -------------------------------------------------------------------------------- 1 | import bonesis 2 | 3 | f = bonesis.BooleanNetwork({ 4 | "a": "c & (!a | !b)", 5 | "b": "c & a", 6 | "c": "a|b|c", 7 | }) 8 | 9 | bo = bonesis.BoNesis(f) 10 | x = bo.cfg() 11 | ~bo.obs({"a": 1, "b": 0, "c": 0}) >= bo.in_attractor(x) != bo.obs({"a": 1, "b": 1}) 12 | 13 | for v in x.assignments(): 14 | print(v) 15 | for v in x.assignments(scope=["a","b"]): 16 | print(v) 17 | -------------------------------------------------------------------------------- /examples/control_attractors.py: -------------------------------------------------------------------------------- 1 | import bonesis 2 | 3 | dom = bonesis.BooleanNetwork({ 4 | "a": "a", 5 | "b": "a & c", 6 | "c": "!b"}) 7 | data = { 8 | "never_b": {"b": 0}, 9 | } 10 | 11 | bo = bonesis.BoNesis(dom, data) 12 | bad_control = bo.Some() 13 | with bo.mutant(bad_control) as m: 14 | m.fixed(bo.obs("never_b")) 15 | 16 | for res in bad_control.complementary_assignments(): 17 | print(res) 18 | -------------------------------------------------------------------------------- /tests/all_attractors.py: -------------------------------------------------------------------------------- 1 | import bonesis 2 | 3 | pkn = bonesis.InfluenceGraph.complete("abc", -1, loops=False) 4 | data = { 5 | "fp0": {"a": 0, "b": 0, "c": 0}, 6 | } 7 | bo = bonesis.BoNesis(pkn, data) 8 | bo.all_attractors_overlap(bo.obs("fp0")) 9 | 10 | bns = bo.boolean_networks(limit=10) 11 | print(bns.standalone()) 12 | for bn in bns: 13 | print("-"*20) 14 | print(bn) 15 | print(list(bn.attractors())) 16 | -------------------------------------------------------------------------------- /examples/dedekind.py: -------------------------------------------------------------------------------- 1 | 2 | import sys 3 | 4 | import bonesis 5 | 6 | bonesis.enable_debug() 7 | 8 | n = int(sys.argv[1]) if len(sys.argv) > 1 else 4 9 | dom = bonesis.InfluenceGraph.all_on_one(n) 10 | 11 | bo = bonesis.BoNesis(dom) 12 | 13 | for i in range(1,n): 14 | bo.constant(i, False) 15 | 16 | bns = bo.boolean_networks() 17 | print(bns.standalone()) 18 | print(bns.count()) 19 | for bn in bns: 20 | print(bn[0]) 21 | 22 | 23 | -------------------------------------------------------------------------------- /examples/view_configurations.py: -------------------------------------------------------------------------------- 1 | import bonesis 2 | 3 | pkn = bonesis.InfluenceGraph.complete("abc", sign=1, loops=False, 4 | allow_skipping_nodes=False) 5 | 6 | data = { 7 | "x": {"a": 0, "b": 0}, 8 | "y": {"a": 1, "b": 1}, 9 | } 10 | 11 | bo = bonesis.BoNesis(pkn, data) 12 | ~bo.obs("x") >= ~bo.obs("y") 13 | 14 | for bn, cfgs in bo.boolean_networks(limit=3, extra="configurations"): 15 | print("-"*30) 16 | print(bn, cfgs) 17 | print("-"*30) 18 | -------------------------------------------------------------------------------- /examples/contexts.py: -------------------------------------------------------------------------------- 1 | import bonesis 2 | 3 | dom = bonesis.InfluenceGraph.complete("abc") 4 | 5 | bo = bonesis.BoNesis(dom) 6 | print(bo.reach.iface.stack_manager) 7 | 8 | cfg0 = bo.cfg() 9 | with bo.mutant({"a": 0}) as m: 10 | cfg0 >= m.cfg() 11 | with bo.action({"b": 1}) as n: 12 | cfg1 = n.cfg() 13 | cfg0 >= cfg1 14 | cfg0 >= m.cfg() 15 | cfg0 >= m.cfg() 16 | 17 | bo.aspmodel.make() 18 | print(bo.aspmodel) 19 | 20 | print(bo.is_satisfiable()) 21 | -------------------------------------------------------------------------------- /tests/test_filters.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import bonesis 4 | 5 | class TestFilters(unittest.TestCase): 6 | def setUp(self): 7 | self.dom1 = bonesis.InfluenceGraph.complete("ab", 0) 8 | 9 | def test_nocyclic(self): 10 | bo = bonesis.BoNesis(self.dom1) 11 | view = bo.boolean_networks(no_cyclic_attractors=True) 12 | self.assertEqual(view.count(), 115) # over 196 13 | view = bo.boolean_networks(limit=10, no_cyclic_attractors=True) 14 | self.assertEqual(view.count(), 10) 15 | -------------------------------------------------------------------------------- /examples/spec_state_equalities.py: -------------------------------------------------------------------------------- 1 | import bonesis 2 | 3 | pkn = bonesis.InfluenceGraph.complete("abc", sign=1, loops=False, 4 | allow_skipping_nodes=False) 5 | 6 | data = { 7 | "x": {"a": 0, "b": 0}, 8 | "y": {"a": 1, "b": 1}, 9 | } 10 | 11 | bo = bonesis.BoNesis(pkn, data) 12 | 13 | x = ~bo.obs("x") 14 | y = ~bo.obs("y") 15 | 16 | x >= y 17 | 18 | x["c"] = y["c"] 19 | 20 | for bn, cfgs in bo.boolean_networks(limit=3, extra="configurations"): 21 | print("-"*30) 22 | print(bn, cfgs) 23 | print("-"*30) 24 | -------------------------------------------------------------------------------- /tests/mutant_all_attractors.py: -------------------------------------------------------------------------------- 1 | import bonesis 2 | 3 | pkn = bonesis.InfluenceGraph.complete("abc", -1, loops=False) 4 | data = { 5 | "a0": {"a": 0, "b": 0}, 6 | "a1": {"a": 1, "b": 1}, 7 | } 8 | bo = bonesis.BoNesis(pkn, data) 9 | 10 | bo.all_attractors_overlap({bo.obs(a) for a in data}) 11 | 12 | with bo.mutant({"c": 1}) as m: 13 | m.all_attractors_overlap(bo.obs("a0")) 14 | 15 | bns = bo.boolean_networks(limit=10) 16 | print(bns.standalone()) 17 | for bn in bns: 18 | print("-"*20) 19 | print(bn) 20 | print(list(bn.attractors())) 21 | -------------------------------------------------------------------------------- /examples/extra_somes.py: -------------------------------------------------------------------------------- 1 | import bonesis 2 | 3 | dom = bonesis.InfluenceGraph.complete(["a","b","c"]) 4 | data = { 5 | "never_b": {"b": 0}, 6 | } 7 | 8 | bo = bonesis.BoNesis(dom, data) 9 | bad_control = bo.Some(min_size=1) 10 | with bo.mutant(bad_control) as m: 11 | m.fixed(bo.obs("never_b")) 12 | s2 = bo.Some(min_size=1, name="s2") 13 | 14 | bad_control != s2 15 | 16 | with bo.mutant(s2) as m: 17 | m.fixed(bo.obs("never_b")) 18 | 19 | for bn, cfgs, somes in bo.boolean_networks(extra=("configurations", "somes"), limit=3): 20 | print(bn, cfgs, somes) 21 | -------------------------------------------------------------------------------- /tests/allreach_attractors.py: -------------------------------------------------------------------------------- 1 | import bonesis 2 | import pandas as pd 3 | 4 | from bonesis.language import * 5 | 6 | pkn = bonesis.InfluenceGraph.complete("abc", -1) 7 | data = { 8 | "x": {"a": 0, "b": 0, "c": 1}, 9 | "y": {"a": 1, "b": 1}, 10 | } 11 | bo = bonesis.BoNesis(pkn, data) 12 | 13 | +bo.obs("x") >> {bo.obs("y")} 14 | 15 | bns = bo.boolean_networks(limit=10) 16 | print(bns.standalone()) 17 | for bn in bns: 18 | print("-"*20) 19 | print(bn) 20 | ait = bn.attractors(reachable_from=data["x"]) 21 | cols = list(sorted(data["y"].keys())) 22 | a = pd.DataFrame(ait)[cols].sort_values(by=cols).drop_duplicates() 23 | print(a) 24 | 25 | -------------------------------------------------------------------------------- /examples/control_fixpoints.py: -------------------------------------------------------------------------------- 1 | import bonesis 2 | 3 | dom = bonesis.BooleanNetwork({ 4 | "a": "c", 5 | "b": "a", 6 | "c": "b"}) 7 | 8 | M = {a:1 for a in dom} 9 | 10 | bo = bonesis.BoNesis(dom) 11 | with bo.mutant(bo.Some("Ensure111", max_size=2)) as m: 12 | m.all_fixpoints(bo.obs(M)) 13 | 14 | view = bo.assignments() 15 | print(view.standalone()) 16 | for res in view: 17 | print(res) 18 | 19 | for res in bo.assignments(solutions="all"): 20 | print(res) 21 | 22 | bo = bonesis.BoNesis(dom) 23 | control = bo.Some() 24 | with bo.mutant(control) as m: 25 | m.all_fixpoints(bo.obs(M)) 26 | 27 | for res in control.assignments(): 28 | print(res) 29 | -------------------------------------------------------------------------------- /tests/allreach_fixpoints.py: -------------------------------------------------------------------------------- 1 | import bonesis 2 | import mpbn 3 | import pandas as pd 4 | 5 | from bonesis.language import * 6 | 7 | pkn = bonesis.InfluenceGraph.complete("abc", 1) 8 | data = { 9 | "x": {"a": 0, "b": 0, "c": 1}, 10 | "y": {"a": 1, "b": 1}, 11 | } 12 | bo = bonesis.BoNesis(pkn, data) 13 | 14 | +bo.obs("x") >> "fixpoints" ^ {bo.obs("y")} 15 | 16 | bns = bo.boolean_networks(limit=10) 17 | print(bns.standalone()) 18 | for bn in bns: 19 | print("-"*20) 20 | print(bn) 21 | ait = bn.attractors(reachable_from=data["x"]) 22 | cols = list(sorted(data["y"].keys())) 23 | a = pd.DataFrame(ait)[cols].sort_values(by=cols).drop_duplicates() 24 | print(a) 25 | 26 | -------------------------------------------------------------------------------- /examples/all_fixpoints.py: -------------------------------------------------------------------------------- 1 | import bonesis 2 | 3 | pkn = bonesis.InfluenceGraph.complete("abc", -1, loops=False) 4 | data = { 5 | "fp0": {"a": 0, "b": 0, "c": 0}, 6 | "110": {"a": 1, "b": 1, "c": 0}, 7 | } 8 | bo = bonesis.BoNesis(pkn, data) 9 | bo.fixed(~bo.obs("fp0")) 10 | bo.all_fixpoints(bo.obs("fp0")) 11 | 12 | bns = bo.boolean_networks(limit=10) 13 | print(bns.standalone()) 14 | for bn in bns: 15 | print("-"*20) 16 | print(bn) 17 | print(list(bn.attractors())) 18 | 19 | with bo.mutant({"a": 1, "b": 1}) as m1: 20 | m1.fixed(~m1.obs("110")) 21 | m1.all_fixpoints(m1.obs("110")) 22 | print(bns.standalone()) 23 | for bn in bns: 24 | print("-"*20) 25 | print(bn) 26 | -------------------------------------------------------------------------------- /tests/hypercubes.py: -------------------------------------------------------------------------------- 1 | import bonesis 2 | 3 | dom = bonesis.InfluenceGraph.complete("abc", sign=1, exact=True, loops=False) 4 | 5 | dom2 = bonesis.BooleanNetwork({ 6 | "a": "c", 7 | "b": "a", 8 | "c": "b"}) 9 | 10 | bo = bonesis.BoNesis(dom) 11 | 12 | """ 13 | x = bo.cfg() 14 | bo.in_attractor(x) 15 | 16 | for i, sol in enumerate(x.assignments()): 17 | print(i, sol) 18 | 19 | """ 20 | #h = bo.hypercube({"a": 1}) 21 | h = bo.hypercube() 22 | bo.fixed(h) 23 | 24 | bo.aspmodel.make() 25 | print(str(bo.aspmodel)) 26 | 27 | assert bo.is_satisfiable() 28 | 29 | for i, sol in enumerate(h.assignments()): 30 | print(i, sol) 31 | for i, sol in enumerate(h.assignments(scope=["a"])): 32 | print(i, sol) 33 | -------------------------------------------------------------------------------- /tests/test_utils.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | class TestOverlayedDict(unittest.TestCase): 4 | def test(self): 5 | from bonesis.utils import OverlayedDict 6 | d = {"a": 1, "b": 2} 7 | od = OverlayedDict(d) 8 | self.assertEqual(set(od.keys()), {"a", "b"}) 9 | self.assertEqual(set(od.items()), {("a", 1), ("b", 2)}) 10 | self.assertEqual(od.get("a"), 1) 11 | self.assertEqual(od["a"], 1) 12 | od["a"] = 3 13 | self.assertEqual(set(od.keys()), {"a", "b"}) 14 | self.assertEqual(set(od.items()), {("a", 3), ("b", 2)}) 15 | self.assertEqual(od.get("a"), 3) 16 | self.assertEqual(od["a"], 3) 17 | self.assertEqual(d, {"a": 1, "b": 2}) 18 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | strategy: 10 | max-parallel: 4 11 | matrix: 12 | python-version: ['3.10', '3.11', '3.12'] 13 | clingo-version: ['5.7', '5.8'] 14 | 15 | steps: 16 | - uses: actions/checkout@v3 17 | - uses: actions/setup-python@v4 18 | with: 19 | python-version: ${{ matrix.python-version }} 20 | - name: Install dependencies 21 | run: | 22 | python -m pip install "clingo==${{ matrix.clingo-version }}.*" 23 | python -m pip install biodivine_aeon 24 | python -m pip install . 25 | - name: Test with pytest 26 | run: | 27 | pip install pytest 28 | pytest 29 | -------------------------------------------------------------------------------- /examples/optimization.py: -------------------------------------------------------------------------------- 1 | 2 | import bonesis 3 | 4 | pkn = bonesis.InfluenceGraph.complete("abc", sign=1, loops=False, allow_skipping_nodes=True) 5 | 6 | data = { 7 | "x": {"a": 0, "b": 0}, 8 | "y": {"a": 1, "b": 1}, 9 | } 10 | 11 | bo = bonesis.BoNesis(pkn, data) 12 | 13 | ~bo.obs("x") >= ~bo.obs("y") 14 | 15 | bo.maximize_nodes() 16 | 17 | view = bonesis.NodesView(bo) 18 | print(view.standalone()) 19 | print(view.count()) 20 | for nodes in view: 21 | print(nodes) 22 | 23 | bo.maximize_strong_constants() 24 | 25 | def interm(nodes): 26 | print("Intermediate model", nodes) 27 | 28 | print("-"*10) 29 | view = bonesis.NonStrongConstantNodesView(bo, intermediate_model_cb=interm) 30 | 31 | print(view.standalone()) 32 | for nodes in view: 33 | print(nodes) 34 | 35 | 36 | -------------------------------------------------------------------------------- /conda/meta.yaml: -------------------------------------------------------------------------------- 1 | {% set name = "bonesis" %} 2 | {% set version = "9999" %} 3 | 4 | package: 5 | name: '{{ name|lower }}' 6 | version: '{{ version }}' 7 | 8 | source: 9 | path: ../ 10 | 11 | build: 12 | script: python -m pip install --no-deps --ignore-installed . 13 | noarch: python 14 | 15 | requirements: 16 | host: 17 | - python 18 | - pip 19 | 20 | run: 21 | - python 22 | - boolean.py 23 | - clingo >=5.5 24 | - colomoto_jupyter 25 | - mpbn >=3.3 26 | - networkx 27 | - numpy 28 | - pandas 29 | - scipy 30 | 31 | test: 32 | imports: 33 | - bonesis 34 | 35 | about: 36 | home: https://github.com/bnediction/bonesis 37 | summary: 'Synthesis of Most Permissive Boolean Networks' 38 | license: 'CeCILL 2.1' 39 | license_file: LICENSE.txt 40 | -------------------------------------------------------------------------------- /tests/test_aeon.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import os 4 | import tempfile 5 | import shutil 6 | 7 | import bonesis 8 | import bonesis.aeon 9 | 10 | class TestAEONImport(unittest.TestCase): 11 | def setUp(self): 12 | self.test_dir = tempfile.mkdtemp() 13 | 14 | def tearDown(self): 15 | shutil.rmtree(self.test_dir) 16 | 17 | 18 | def test_import_with_constant(self): 19 | """ 20 | Source: https://github.com/bnediction/bonesis/issues/6 21 | """ 22 | fpath = os.path.join(self.test_dir, "test1.aeon") 23 | with open(fpath, "w") as fp: 24 | fp.write("""#name:aeon_test 25 | $A:A & T 26 | A -> A 27 | T -> A 28 | A ->? B 29 | $T:true 30 | """) 31 | 32 | dom = bonesis.aeon.AEONDomain.from_aeon_file(fpath) 33 | bo = bonesis.BoNesis(dom) 34 | self.assertEqual(bo.boolean_networks().count(), 3) 35 | -------------------------------------------------------------------------------- /examples/project_bns.py: -------------------------------------------------------------------------------- 1 | 2 | import bonesis 3 | 4 | dom = bonesis.InfluenceGraph.complete("abc", loops=False) 5 | bo = bonesis.BoNesis(dom) 6 | 7 | projections = bo.projected_boolean_networks() 8 | with projections.view({"a","b"}) as view: 9 | for bn in view: 10 | print(bn) 11 | print(view.count()) 12 | with projections.view({"c"}) as view: 13 | for bn in view: 14 | print(bn) 15 | print(view.count()) 16 | 17 | functions = bo.local_functions() 18 | for node in ["a","b"]: 19 | with functions.view(node) as view: 20 | for f in view: 21 | print(f) 22 | print(view.count()) 23 | 24 | print(functions.as_dict()) 25 | print(functions.as_dict("count")) 26 | print(functions.as_dict("count", keys="ab")) 27 | 28 | dom = bonesis.InfluenceGraph.complete("ab", loops=False, 29 | allow_skipping_nodes=True) 30 | bo = bonesis.BoNesis(dom) 31 | funcs = bo.local_functions(skip_empty=True, solutions="subset-minimal") 32 | with funcs.view("a") as view: 33 | print(list(map(str,view))) 34 | print(view.count()) 35 | -------------------------------------------------------------------------------- /tests/timeout_and_gil.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | import bonesis 4 | 5 | dom = bonesis.InfluenceGraph.complete("abcd", sign=1, canonic=True, exact=True, 6 | maxclause=32) 7 | bo = bonesis.BoNesis(dom) 8 | 9 | bo.fixed(~bo.obs({"a":1})) 10 | for bn in bo.boolean_networks(limit=3): 11 | print(datetime.datetime.now(), "solution") 12 | 13 | bo.all_fixpoints(bo.obs({"a":1})) 14 | 15 | print(datetime.datetime.now(), "solving") 16 | sols = [] 17 | for bn in bo.boolean_networks(limit=3, timeout=5, fail_if_timeout=False): 18 | print(datetime.datetime.now(), "solution") 19 | sols.append(bn) 20 | print(len(sols)) 21 | 22 | sols = [] 23 | try: 24 | print(datetime.datetime.now(), "solving") 25 | for bn in bo.boolean_networks(limit=3, timeout=5): 26 | print(datetime.datetime.now(), "solution") 27 | sols.append(bn) 28 | except TimeoutError: 29 | print("GOT TIMEOUT") 30 | print(len(sols)) 31 | 32 | print(datetime.datetime.now(), "solving") 33 | for bn in bo.boolean_networks(limit=3, timeout=5): 34 | print(datetime.datetime.now(), "solution") 35 | sols.append(bn) 36 | -------------------------------------------------------------------------------- /tests/timeout_and_gil_diverse.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | import bonesis 4 | 5 | dom = bonesis.InfluenceGraph.complete("abcd", sign=1, canonic=True, exact=True, 6 | maxclause=32) 7 | bo = bonesis.BoNesis(dom) 8 | 9 | bo.fixed(~bo.obs({"a":1})) 10 | for bn in bo.diverse_boolean_networks(limit=3): 11 | print(datetime.datetime.now(), "solution") 12 | 13 | bo.all_fixpoints(bo.obs({"a":1})) 14 | 15 | print(datetime.datetime.now(), "solving") 16 | sols = [] 17 | for bn in bo.diverse_boolean_networks(limit=3):#, timeout=5, fail_if_timeout=False): 18 | print(datetime.datetime.now(), "solution") 19 | sols.append(bn) 20 | print(len(sols)) 21 | 22 | sols = [] 23 | try: 24 | print(datetime.datetime.now(), "solving") 25 | for bn in bo.diverse_boolean_networks(limit=3, timeout=5): 26 | print(datetime.datetime.now(), "solution") 27 | sols.append(bn) 28 | except TimeoutError: 29 | print("GOT TIMEOUT") 30 | print(len(sols)) 31 | 32 | print(datetime.datetime.now(), "solving") 33 | for bn in bo.diverse_boolean_networks(limit=3, timeout=5): 34 | print(datetime.datetime.now(), "solution") 35 | sols.append(bn) 36 | -------------------------------------------------------------------------------- /examples/count_matching.py: -------------------------------------------------------------------------------- 1 | from tqdm import tqdm 2 | import bonesis 3 | 4 | dom1 = bonesis.InfluenceGraph.complete("abc", 1) 5 | #dom1 = bonesis.InfluenceGraph.complete("ab", 0) 6 | 7 | bo = bonesis.BoNesis(dom1) 8 | bns = bo.boolean_networks() 9 | 10 | def count_matching(m): 11 | print(m, len([bn for bn in tqdm(bns) if m(bn)])) 12 | 13 | def all_fixpoints_0(bn): 14 | A = list(bn.attractors()) 15 | return A == [{"a": 0, "b": 0, "c": 0}] 16 | #count_matching(all_fixpoints_0) 17 | 18 | def all_fixpoints_0_mutant(bn): 19 | if all_fixpoints_0(bn): 20 | bn["a"] = True 21 | A = list(bn.attractors()) 22 | return A == [{"a": 1, "b": 1, "c": 1}] 23 | return False 24 | #count_matching(all_fixpoints_0_mutant) 25 | 26 | def all_fixpoints_mutant(bn): 27 | bn["a"] = True 28 | A = list(bn.attractors()) 29 | return A == [{"a": 1, "b": 1, "c": 1}] 30 | #count_matching(all_fixpoints_mutant) 31 | 32 | def fixpoints_0(bn): 33 | A = list(bn.attractors()) 34 | return {"a": 0, "b": 0, "c": 0} in A 35 | #count_matching(fixpoints_0) 36 | 37 | def no_cyclic(bn): 38 | return not bn.has_cyclic_attractor() 39 | #count_matching(no_cyclic) 40 | -------------------------------------------------------------------------------- /tests/test_universal.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import bonesis 4 | 5 | class TestUniversal(unittest.TestCase): 6 | def setUp(self): 7 | self.dom1 = bonesis.InfluenceGraph.complete("abc", 1) 8 | self.data1 = { 9 | "0": {"a": 0, "b": 0, "c": 0}, 10 | "1": {"a": 1, "b": 1, "c": 1}, 11 | } 12 | 13 | def test_all_fixpoints(self): 14 | bo = bonesis.BoNesis(self.dom1, self.data1) 15 | bo.fixed(bo.obs("0")) 16 | bo.all_fixpoints({bo.obs("0")}) 17 | self.assertEqual(bo.boolean_networks().count(), 355) 18 | bo.all_fixpoints({bo.obs("0"), bo.obs("1")}) 19 | self.assertEqual(bo.boolean_networks().count(), 355) 20 | bo.all_fixpoints({bo.obs("1")}) 21 | self.assertEqual(bo.boolean_networks().count(), 0) 22 | 23 | def test_all_fixpoints_mutant(self): 24 | bo = bonesis.BoNesis(self.dom1, self.data1) 25 | with bo.mutant({"a": 1}) as m: 26 | m.fixed(~m.obs("1")) 27 | m.all_fixpoints({m.obs("1")}) 28 | self.assertEqual(bo.boolean_networks().count(), 2640) 29 | bo.fixed(~bo.obs("0")) 30 | bo.all_fixpoints({bo.obs("0")}) 31 | self.assertEqual(bo.boolean_networks().count(), 25) 32 | -------------------------------------------------------------------------------- /tests/test_configurations.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import bonesis 4 | 5 | class TestConfiguration(unittest.TestCase): 6 | def test_node_equal1(self): 7 | pkn = bonesis.InfluenceGraph.complete("abc", sign=1, loops=False, 8 | allow_skipping_nodes=False) 9 | data = { 10 | "x": {"a": 0}, 11 | "y": {"a": 1}, 12 | } 13 | bo = bonesis.BoNesis(pkn, data) 14 | x = ~bo.obs("x") 15 | y = ~bo.obs("y") 16 | x >= y 17 | x["c"] = y["c"] 18 | 19 | for _, cfgs in bo.boolean_networks(limit=15, extra="configurations"): 20 | self.assertEqual(cfgs["x"]["c"], cfgs["y"]["c"]) 21 | 22 | def test_node_diff1(self): 23 | pkn = bonesis.InfluenceGraph.complete("abc", sign=1, loops=False, 24 | allow_skipping_nodes=False) 25 | data = { 26 | "x": {"a": 0}, 27 | "y": {"a": 1}, 28 | } 29 | bo = bonesis.BoNesis(pkn, data) 30 | x = ~bo.obs("x") 31 | y = ~bo.obs("y") 32 | x >= y 33 | x["c"] != y["c"] 34 | 35 | for _, cfgs in bo.boolean_networks(limit=15, extra="configurations"): 36 | self.assertNotEqual(cfgs["x"]["c"], cfgs["y"]["c"]) 37 | -------------------------------------------------------------------------------- /tests/test_view_extra.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import bonesis 4 | import mpbn 5 | 6 | class testExtraView(unittest.TestCase): 7 | def setUp(self): 8 | pkn = bonesis.InfluenceGraph.complete("abc", sign=1, loops=False, 9 | allow_skipping_nodes=False) 10 | data = { 11 | "x": {"a": 0, "b": 0}, 12 | "y": {"a": 1, "b": 1}, 13 | } 14 | bo = bonesis.BoNesis(pkn, data) 15 | ~bo.obs("x") >= ~bo.obs("y") 16 | self.bo1 = bo 17 | 18 | def test_no_extra(self): 19 | c = 2 20 | for bn in self.bo1.boolean_networks(limit=c): 21 | self.assertIsInstance(bn, mpbn.MPBooleanNetwork) 22 | c -= 1 23 | self.assertEqual(c, 0) 24 | 25 | def test_extra_configurations(self): 26 | for view in [self.bo1.boolean_networks, 27 | self.bo1.diverse_boolean_networks]: 28 | c = 2 29 | for bn, cfg in view(limit=c, extra="configurations"): 30 | self.assertIsInstance(bn, mpbn.MPBooleanNetwork) 31 | self.assertIsInstance(cfg, dict) 32 | self.assertEqual(set(cfg.keys()), {"x","y"}) 33 | c -= 1 34 | self.assertEqual(c, 0) 35 | -------------------------------------------------------------------------------- /tests/allreach_fixpoints_mutants.py: -------------------------------------------------------------------------------- 1 | import bonesis 2 | from mpbn import MPBooleanNetwork 3 | import pandas as pd 4 | 5 | from bonesis.language import * 6 | 7 | pkn = bonesis.InfluenceGraph.complete("abc", 1) 8 | data = { 9 | "x": {"a": 0, "b": 0}, 10 | "y": {"a": 1, "b": 1}, 11 | } 12 | bo = bonesis.BoNesis(pkn, data) 13 | 14 | fixed(~bo.obs("x")) 15 | all_fixpoints({bo.obs("x")}) 16 | with bo.mutant({"c":1}) as mc: 17 | y = mc.obs("y") 18 | for cfg in bonesis.matching_configurations(mc.obs("x")): 19 | cfg >= mc.fixed(+y) 20 | cfg >> "fixpoints" ^ {y} 21 | 22 | def validate(bn): 23 | print("# all fixpoints") 24 | fps = [a for a in bn.attractors() if '*' not in a.values()] 25 | print(pd.DataFrame(fps)) 26 | print("# reachable from x with mutation") 27 | bn["c"] = True 28 | orig = data["x"].copy() 29 | orig["c"] = 1 30 | fps = [a for a in bn.attractors(reachable_from=orig) if '*' not in a.values()] 31 | print(pd.DataFrame(fps)) 32 | 33 | 34 | bn = MPBooleanNetwork({ 35 | "a": "b&c", 36 | "b": "c", 37 | "c": "0", 38 | }) 39 | print(bn) 40 | validate(bn) 41 | print("="*40) 42 | 43 | bns = bo.boolean_networks(limit=3) 44 | bns.standalone(output_filename="/tmp/test.sh") 45 | found = False 46 | for bn in bns: 47 | found = True 48 | print("-"*20) 49 | print(bn) 50 | validate(bn) 51 | print(found) 52 | 53 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=60"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "bonesis" 7 | version = "9999" 8 | dependencies = [ 9 | "boolean.py", 10 | "clingo>=5.5", 11 | "colomoto_jupyter", 12 | "mpbn>=3.3", 13 | "networkx", 14 | "numpy", 15 | "pandas", 16 | "scipy", 17 | ] 18 | requires-python = ">=3.9" 19 | authors = [ 20 | {name = "Loïc Paulevé", email = "loic.pauleve@cnrs.fr"}, 21 | ] 22 | description = "Synthesis of Most Permissive Boolean Networks" 23 | readme = "README.md" 24 | license = "CECILL-2.0" 25 | license-files = ['LICENSE.txt', 'Licence_CeCILL_V2.1-fr.txt'] 26 | keywords = ["computational systems biology", "boolean networks", "model synthesis"] 27 | classifiers= [ 28 | "Intended Audience :: Science/Research", 29 | "Topic :: Scientific/Engineering :: Bio-Informatics", 30 | ] 31 | 32 | [project.scripts] 33 | bonesis-attractors = "bonesis.cli:main_attractors" 34 | bonesis-reprogramming = "bonesis.cli:main_reprogramming" 35 | bonesis-utils = "bonesis.cli:main_utils" 36 | 37 | [project.urls] 38 | Homepage = "https://bnediction.github.io/bonesis/" 39 | Repository = "https://github.com/bnediction/bonesis" 40 | 41 | [tool.setuptools.packages.find] 42 | exclude = ["conda*"] 43 | 44 | [tool.setuptool.package-data] 45 | bonesis0 = ['*.cfg'] 46 | -------------------------------------------------------------------------------- /tests/test_language.py: -------------------------------------------------------------------------------- 1 | 2 | import unittest 3 | 4 | import bonesis 5 | import bonesis.language as bol 6 | 7 | class LanguageTest(unittest.TestCase): 8 | def setUp(self): 9 | self.dom = bonesis.InfluenceGraph.complete("abc", 0) 10 | self.data = { 11 | "x": {"a": 0}, 12 | "y": {"b": 0}, 13 | "z": {"a": 1, "b": 1, "c": 1}} 14 | def _fresh_bo(self): 15 | return bonesis.BoNesis(self.dom, self.data) 16 | 17 | def test_different(self): 18 | bo = self._fresh_bo() 19 | x = ~bo.obs("x") 20 | y = ~bo.obs("y") 21 | self.assertIsInstance(x != y, bol.different) 22 | fp_x = bo.fixed(x) 23 | fp_y = bo.fixed(y) 24 | self.assertIsInstance(fp_x != fp_y, bol.different) 25 | self.assertIsInstance(x != fp_y, bol.different) 26 | self.assertIsInstance(fp_x != y, bol.different) 27 | tp_z = bo.fixed(bo.obs("z")) 28 | with self.assertRaises(TypeError): 29 | tp_z != x 30 | with self.assertRaises(TypeError): 31 | bo.obs("x") != y 32 | 33 | def test_cfg_node_eq(self): 34 | bo = self._fresh_bo() 35 | x = ~bo.obs("x") 36 | y = ~bo.obs("y") 37 | x["c"] = y["c"] 38 | self.assertIsNone(x["c"] == y["c"]) 39 | 40 | def test_cfg_node_ne(self): 41 | bo = self._fresh_bo() 42 | x = ~bo.obs("x") 43 | y = ~bo.obs("y") 44 | self.assertIsNone(x["c"] != y["c"]) 45 | -------------------------------------------------------------------------------- /tests/test_fixed.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import bonesis 4 | 5 | class TestFixed(unittest.TestCase): 6 | def setUp(self): 7 | self.dom1 = bonesis.InfluenceGraph.complete("abc", 1) 8 | self.data1 = { 9 | "000": {"a": 0, "b": 0, "c": 0}, 10 | "111": {"a": 1, "b": 1, "c": 1}, 11 | } 12 | self.dom2 = bonesis.InfluenceGraph.complete("abc", -1) 13 | 14 | def test_fixpoints(self): 15 | bo = bonesis.BoNesis(self.dom1, self.data1) 16 | bo.fixed(~bo.obs("000")) 17 | self.assertEqual(bo.boolean_networks().count(), 6859) 18 | 19 | def test_trapspace(self): 20 | bo = bonesis.BoNesis(self.dom1, self.data1) 21 | bo.fixed(bo.obs("000")) 22 | self.assertEqual(bo.boolean_networks().count(), 6859) 23 | 24 | def test_mintrap(self): 25 | bo = bonesis.BoNesis(self.dom1) 26 | h = bo.hypercube(min_dimension=1) 27 | bo.fixed(h) 28 | self.assertEqual(len(list(h.assignments(limit=1))), 0) 29 | 30 | bo = bonesis.BoNesis(self.dom2) 31 | h = bo.hypercube(min_dimension=2) 32 | bo.fixed(h) 33 | val = next(iter(h.assignments())) 34 | self.assertGreaterEqual(sum((1 for v in val.values() if v == '*')), 2) 35 | 36 | bo = bonesis.BoNesis(self.dom2) 37 | h = bo.hypercube(max_dimension=0) 38 | bo.fixed(h) 39 | val = next(iter(h.assignments())) 40 | self.assertEqual(sum((1 for v in val.values() if v == '*')), 0) 41 | -------------------------------------------------------------------------------- /tests/test_managers.py: -------------------------------------------------------------------------------- 1 | 2 | import unittest 3 | 4 | import bonesis 5 | import bonesis.language as bol 6 | 7 | class ManagersTest(unittest.TestCase): 8 | def setUp(self): 9 | self.dom = bonesis.InfluenceGraph.complete("abc", 0) 10 | self.data = { 11 | "x": {"a": 0}, 12 | "y": {"b": 0}, 13 | "z": {"a": 1, "b": 1, "c": 1}} 14 | def _fresh_bo(self): 15 | return bonesis.BoNesis(self.dom, self.data) 16 | 17 | def test_override(self): 18 | bo = self._fresh_bo() 19 | with bo.scope_reachability(monotone=True): 20 | with bo.scope_reachability(monotone=False): 21 | ~bo.obs("x") >= ~bo.obs("y") 22 | 23 | for pname, args, kwargs in bo.manager.properties: 24 | match pname: 25 | case "reach": 26 | self.assertEqual(kwargs, {"monotone": False}) 27 | 28 | 29 | def test_mixed_cascade(self): 30 | mutant = {"c": 0} 31 | bo = self._fresh_bo() 32 | with bo.scope_reachability(monotone=True): 33 | with bo.mutant(mutant): 34 | ~bo.obs("x") >= ~bo.obs("y") 35 | 36 | for pname, args, kwargs in bo.manager.properties: 37 | match pname: 38 | case "mutant": 39 | self.assertEqual(args, (1, mutant)) 40 | self.assertEqual(kwargs, {}) 41 | case "reach": 42 | self.assertEqual(kwargs, {"mutant": 1, "monotone": True}) 43 | 44 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | pypi-publish: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - run: sed -i "s:9999:${VERSION//*v/}:" pyproject.toml conda/meta.yaml 13 | env: 14 | VERSION: ${{ github.ref }} 15 | - uses: actions/setup-python@v1 16 | - name: Install dependencies 17 | run: | 18 | python -m pip install --upgrade pip 19 | pip install setuptools wheel twine build 20 | - name: Build and publish 21 | env: 22 | TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} 23 | TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} 24 | run: | 25 | python -m build 26 | twine upload dist/* 27 | conda-publish: 28 | runs-on: ubuntu-latest 29 | steps: 30 | - uses: actions/checkout@v2 31 | - run: sed -i "s:9999:${VERSION//*v/}:" pyproject.toml conda/meta.yaml 32 | env: 33 | VERSION: ${{ github.ref }} 34 | - uses: conda-incubator/setup-miniconda@v3 35 | with: 36 | python-version: "3.12" 37 | - name: prepare 38 | run: | 39 | conda install -y python=3.12 anaconda-client conda-build conda-verify 40 | conda config --set anaconda_upload yes 41 | - name: build 42 | run: | 43 | cd conda 44 | conda build --user colomoto --token $ANACONDA_TOKEN -c defaults -c conda-forge -c potassco -c colomoto -c daemontus . 45 | env: 46 | ANACONDA_TOKEN: ${{ secrets.ANACONDA_TOKEN }} 47 | -------------------------------------------------------------------------------- /bonesis0/clingo_solving.py: -------------------------------------------------------------------------------- 1 | import clingo 2 | 3 | def setup_clingo_solve_handler(settings, ctrl): 4 | if True: #settings.get("timeout"): 5 | sh = BoSolveHandle(ctrl, timeout=settings.get("timeout"), 6 | fail_if_timeout=settings.get("fail_if_timeout")) 7 | else: 8 | sh = ctrl.solve(yield_=True) 9 | return sh 10 | 11 | class BoSolveHandle(object): 12 | def __init__(self, clingo_ctrl, timeout=0, fail_if_timeout=True): 13 | self.clingo_sh = clingo_ctrl.solve(async_=True, yield_=True) 14 | self.timeout = timeout 15 | self.fail_if_timeout = fail_if_timeout 16 | 17 | def cancel(self): 18 | self.clingo_sh.cancel() 19 | 20 | def __enter__(self): 21 | self.__exited = False 22 | self.clingo_sh.__enter__() 23 | 24 | def __exit__(self, *args): 25 | if self.__exited: 26 | return 27 | self.__exited = True 28 | self.clingo_sh.__exit__(*args) 29 | 30 | def __iter__(self): 31 | with self: 32 | while True: 33 | self.clingo_sh.resume() 34 | if self.timeout > 0: 35 | ready = self.clingo_sh.wait(self.timeout) 36 | if not ready: # time out 37 | self.clingo_sh.cancel() 38 | if self.fail_if_timeout: 39 | raise TimeoutError 40 | else: 41 | break 42 | else: 43 | ready = self.clingo_sh.wait() 44 | m = self.clingo_sh.model() 45 | if m is None: 46 | break 47 | yield m 48 | -------------------------------------------------------------------------------- /bonesis/debug.py: -------------------------------------------------------------------------------- 1 | # Copyright or © or Copr. Loïc Paulevé (2023) 2 | # 3 | # loic.pauleve@cnrs.fr 4 | # 5 | # This software is governed by the CeCILL license under French law and 6 | # abiding by the rules of distribution of free software. You can use, 7 | # modify and/ or redistribute the software under the terms of the CeCILL 8 | # license as circulated by CEA, CNRS and INRIA at the following URL 9 | # "http://www.cecill.info". 10 | # 11 | # As a counterpart to the access to the source code and rights to copy, 12 | # modify and redistribute granted by the license, users are provided only 13 | # with a limited warranty and the software's author, the holder of the 14 | # economic rights, and the successive licensors have only limited 15 | # liability. 16 | # 17 | # In this respect, the user's attention is drawn to the risks associated 18 | # with loading, using, modifying and/or developing or reproducing the 19 | # software by the user in light of its specific status of free software, 20 | # that may mean that it is complicated to manipulate, and that also 21 | # therefore means that it is reserved for developers and experienced 22 | # professionals having in-depth computer knowledge. Users are therefore 23 | # encouraged to load and test the software's suitability as regards their 24 | # requirements in conditions enabling the security of their systems and/or 25 | # data to be ensured and, more generally, to use and operate it in the 26 | # same conditions as regards security. 27 | # 28 | # The fact that you are presently reading this means that you have had 29 | # knowledge of the CeCILL license and that you accept its terms. 30 | # 31 | 32 | import sys 33 | 34 | __enabled = False 35 | 36 | def enable_debug(): 37 | global __enabled 38 | __enabled = True 39 | 40 | def disable_debug(): 41 | global __enabled 42 | __enabled = False 43 | 44 | def debug_enabled(): 45 | return __enabled 46 | 47 | def dbg(msg): 48 | if debug_enabled(): 49 | print(f"bonesis: {msg}", file=sys.stderr) 50 | 51 | -------------------------------------------------------------------------------- /bonesis0/subset_portfolio.cfg: -------------------------------------------------------------------------------- 1 | [solver.0]: --heuristic=Vsids,92 --restarts=L,60 --deletion=basic,50 --del-max=2000000 --del-estimate=1 --del-cfl=+,2000,100,20 --del-grow=0 --del-glue=2,0 --strengthen=recursive,all --otfs=2 --init-moms --score-other=all --update-lbd=less --save-progress=160 --init-watches=least --local-restarts --loops=shared --opt-strat=bb,hier 2 | [solver.1]: --heuristic=Vsids --restarts=D,100,0.7 --deletion=basic,50 --del-init=3.0,500,19500 --del-grow=1.1,20.0,x,100,1.5 --del-cfl=+,10000,2000 --del-glue=2 --strengthen=recursive --update-lbd=less --otfs=2 --save-p=75 --counter-restarts=3,1023 --reverse-arcs=2 --contraction=250 --loops=common --opt-heu=sign --opt-strat=usc,disjoint 3 | [solver.4]: --heuristic=Vsids --restarts=L,100 --deletion=basic,75,mixed --del-init=3.0,1000,20000 --del-grow=1.1,25,x,100,1.5 --del-cfl=x,10000,1.1 --del-glue=2 --update-lbd=glucose --strengthen=recursive --otfs=2 --save-p=70 --restart-on-model --opt-heu=sign,model --opt-strat=bb,inc 4 | [solver.5]: --heuristic=Vsids --restarts=D,100,0.7 --deletion=sort,50,mixed --del-max=200000 --del-init=20.0,1000,14000 --del-cfl=+,4000,600 --del-glue=2 --update-lbd=less --strengthen=recursive --otfs=2 --save-p=20 --contraction=600 --loops=distinct --counter-restarts=7,1023 --reverse-arcs=2 5 | [solver.7]: --heuristic=Vsids --reverse-arcs=1 --otfs=1 --local-restarts --save-progress=0 --contraction=250 --counter-restart=7,200 --restarts=x,100,1.5 --del-init=3.0,800,-1 --deletion=basic,60 --strengthen=local --del-grow=1.0,1.0 --del-glue=4 --del-cfl=+,4000,300,100 6 | [solver.8]: --heuristic=Vsids --restarts=L,256 --counter-restart=3,9973 --strengthen=recursive --update-lbd=less --del-glue=2 --otfs=2 --deletion=ipSort,75,mixed --del-init=20.0,1000,19000 7 | [solver.11]: --heuristic=Vsids --strengthen=recursive --restarts=x,100,1.5,15 --contraction=0 8 | [solver.12]: --heuristic=Vsids --restarts=L,128 --save-p --otfs=1 --init-w=least --contr=0 --opt-heu=sign,model 9 | [solver.15]: --heuristic=Vsids --restarts=D,100,0.7 --deletion=sort,50,mixed --del-max=200000 --del-init=20.0,1000,14000 --del-cfl=+,4000,600 --del-glue=2 --update-lbd=less --strengthen=recursive --otfs=2 --save-p=20 --contraction=600 --counter-restarts=7,1023 --reverse-arcs=2 10 | -------------------------------------------------------------------------------- /bonesis/snippets.py: -------------------------------------------------------------------------------- 1 | # Copyright or © or Copr. Loïc Paulevé (2023) 2 | # 3 | # loic.pauleve@cnrs.fr 4 | # 5 | # This software is governed by the CeCILL license under French law and 6 | # abiding by the rules of distribution of free software. You can use, 7 | # modify and/ or redistribute the software under the terms of the CeCILL 8 | # license as circulated by CEA, CNRS and INRIA at the following URL 9 | # "http://www.cecill.info". 10 | # 11 | # As a counterpart to the access to the source code and rights to copy, 12 | # modify and redistribute granted by the license, users are provided only 13 | # with a limited warranty and the software's author, the holder of the 14 | # economic rights, and the successive licensors have only limited 15 | # liability. 16 | # 17 | # In this respect, the user's attention is drawn to the risks associated 18 | # with loading, using, modifying and/or developing or reproducing the 19 | # software by the user in light of its specific status of free software, 20 | # that may mean that it is complicated to manipulate, and that also 21 | # therefore means that it is reserved for developers and experienced 22 | # professionals having in-depth computer knowledge. Users are therefore 23 | # encouraged to load and test the software's suitability as regards their 24 | # requirements in conditions enabling the security of their systems and/or 25 | # data to be ensured and, more generally, to use and operate it in the 26 | # same conditions as regards security. 27 | # 28 | # The fact that you are presently reading this means that you have had 29 | # knowledge of the CeCILL license and that you accept its terms. 30 | # 31 | 32 | import itertools 33 | 34 | def matching_configurations(obs): 35 | bo = obs.mgr.bo 36 | nodes = list(bo.domain) 37 | mutations = set(obs.mgr.mutations if hasattr(obs.mgr, "mutations") else []) 38 | known = mutations.union(bo.data[obs.name]) 39 | missing = [n for n in nodes if n not in known] 40 | for assigns in itertools.product((0,1), repeat=len(missing)): 41 | cfg = +obs 42 | for (n, b) in zip(missing, assigns): 43 | cfg[n] = b 44 | yield cfg 45 | 46 | def bn_nocyclic_attractors(bn): 47 | return not bn.has_cyclic_attractor() 48 | 49 | def all_different(cfgs): 50 | for a, b in itertools.combinations(cfgs, 2): 51 | a != b 52 | -------------------------------------------------------------------------------- /bonesis0/gil_utils.py: -------------------------------------------------------------------------------- 1 | from queue import Queue 2 | from threading import Thread 3 | import signal 4 | 5 | def setup_gil_iterator(settings, it, sh, ctl): 6 | g = settings.get("clingo_gil_workaround") 7 | soft = settings["soft_interrupt"] 8 | if g == 1: 9 | it = BGIteratorPersistent(it, sh, ctl, soft_interrupt=soft) 10 | elif g == 2: 11 | it = BGIteratorOnDemand(it, sh, ctl, soft_interrupt=soft) 12 | return it 13 | 14 | class BGIteratorOnDemand: 15 | """ 16 | Creates a thread at each iteration 17 | """ 18 | def __init__(self, it, sh, ctl, soft_interrupt=False): 19 | self.it = it 20 | self.ctl = ctl 21 | self.sh = sh 22 | self.q = Queue(1) 23 | self.soft_interrupt = soft_interrupt 24 | 25 | def __next__(self): 26 | def proxy(): 27 | elt = next(self.it, None) 28 | self.q.put(elt) 29 | t = Thread(target=proxy, daemon=True) 30 | t.start() 31 | try: 32 | elt = self.q.get() 33 | except KeyboardInterrupt: 34 | self.sh.cancel() 35 | self.ctl.interrupt() 36 | elt = None 37 | if not self.soft_interrupt: 38 | raise 39 | if elt is None: 40 | raise StopIteration 41 | t.join() 42 | return elt 43 | 44 | 45 | class BGIteratorPersistent(object): 46 | def __init__(self, it, sh, ctl, soft_interrupt=False): 47 | self.it = it 48 | self.sh = sh 49 | self.ctl = ctl 50 | self.q = Queue(1) 51 | self.w = Queue(1) 52 | self.soft_interrupt = soft_interrupt 53 | def proxy(): 54 | while self.w.get(): 55 | try: 56 | elt = next(self.it, None) 57 | except Exception as e: 58 | self.q.put(e) 59 | break 60 | self.q.put(elt) 61 | if elt is None: 62 | break 63 | self.t = Thread(target=proxy, daemon=True) 64 | self.t.start() 65 | 66 | def __next__(self): 67 | self.w.put(1) 68 | try: 69 | elt = self.q.get() 70 | except KeyboardInterrupt: 71 | self.sh.cancel() 72 | self.ctl.interrupt() 73 | elt = None 74 | if not self.soft_interrupt: 75 | raise 76 | if isinstance(elt, Exception): 77 | raise elt 78 | if elt is None: 79 | raise StopIteration 80 | return elt 81 | 82 | def quit(self): 83 | self.w.put(None) 84 | -------------------------------------------------------------------------------- /bonesis/utils.py: -------------------------------------------------------------------------------- 1 | # Copyright or © or Copr. Loïc Paulevé (2023) 2 | # 3 | # loic.pauleve@cnrs.fr 4 | # 5 | # This software is governed by the CeCILL license under French law and 6 | # abiding by the rules of distribution of free software. You can use, 7 | # modify and/ or redistribute the software under the terms of the CeCILL 8 | # license as circulated by CEA, CNRS and INRIA at the following URL 9 | # "http://www.cecill.info". 10 | # 11 | # As a counterpart to the access to the source code and rights to copy, 12 | # modify and redistribute granted by the license, users are provided only 13 | # with a limited warranty and the software's author, the holder of the 14 | # economic rights, and the successive licensors have only limited 15 | # liability. 16 | # 17 | # In this respect, the user's attention is drawn to the risks associated 18 | # with loading, using, modifying and/or developing or reproducing the 19 | # software by the user in light of its specific status of free software, 20 | # that may mean that it is complicated to manipulate, and that also 21 | # therefore means that it is reserved for developers and experienced 22 | # professionals having in-depth computer knowledge. Users are therefore 23 | # encouraged to load and test the software's suitability as regards their 24 | # requirements in conditions enabling the security of their systems and/or 25 | # data to be ensured and, more generally, to use and operate it in the 26 | # same conditions as regards security. 27 | # 28 | # The fact that you are presently reading this means that you have had 29 | # knowledge of the CeCILL license and that you accept its terms. 30 | # 31 | 32 | from itertools import chain 33 | 34 | def frozendict(d): 35 | return frozenset(d.items()) 36 | 37 | class OverlayedDict(dict): 38 | def __init__(self, parent, *args, **kwargs): 39 | self.parent = parent 40 | self.overlayed = set() 41 | def __getitem__(self, key): 42 | if key in self.overlayed: 43 | return super().__getitem__(key) 44 | return self.parent[key] 45 | def get(self, key, default=None): 46 | if key not in self.overlayed: 47 | return self.parent.get(key, default) 48 | return self[key] 49 | def __setitem__(self, k, v): 50 | self.overlayed.add(k) 51 | super().__setitem__(k, v) 52 | def __contains__(self, k): 53 | return super().__contains__(k) or k in self.parent 54 | def keys(self): 55 | return chain(super().keys(), 56 | [k for k in self.parent.keys() if k not in self.overlayed]) 57 | def items(self): 58 | return chain(super().items(), 59 | [(k,v) for (k,v) in self.parent.items() if k not in self.overlayed]) 60 | def values(self): 61 | return chain(super().values(), 62 | [v for (k,v) in self.parent.items() if k not in self.overlayed]) 63 | 64 | -------------------------------------------------------------------------------- /tests/test_reachability.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import bonesis 4 | 5 | class NonReachTest(unittest.TestCase): 6 | def setUp(self): 7 | self.bn1 = bonesis.BooleanNetwork({"a": "b", "b": "c", "c": 1}) 8 | self.bn2 = bonesis.BooleanNetwork({"a": "b", "b": "c", "c": "c"}) 9 | self.bn3 = bonesis.BooleanNetwork({"a": "b", "b": "c", "c": "!c"}) 10 | self.data = {} 11 | for a in [0,1]: 12 | for b in [0,1]: 13 | for c in [0,1]: 14 | self.data[f"{a}{b}{c}"] = {"a": a, "b": b, "c": c} 15 | 16 | def test_irreflexive(self): 17 | bo = bonesis.BoNesis(self.bn1, self.data) 18 | bo.set_constant("bounded_nonreach", 0) 19 | +bo.obs("000") / +bo.obs("000") 20 | self.assertFalse(bo.is_satisfiable()) 21 | 22 | def test_positive(self): 23 | for x, y in [ 24 | ("001", "000"), 25 | ("000", "010"), 26 | ("000", "110"), 27 | ("000", "100")]: 28 | bo = bonesis.BoNesis(self.bn1, self.data) 29 | bo.set_constant("bounded_nonreach", 0) 30 | +bo.obs(x) / +bo.obs(y) 31 | self.assertTrue(bo.is_satisfiable(), f"nonreach({x},{y})") 32 | 33 | def test_negative(self): 34 | for x, y in [ 35 | ("000", "111"), 36 | ("010", "110"), 37 | ("010", "100")]: 38 | bo = bonesis.BoNesis(self.bn1, self.data) 39 | bo.set_constant("bounded_nonreach", 0) 40 | +bo.obs(x) / +bo.obs(y) 41 | self.assertFalse(bo.is_satisfiable(), f"nonreach({x},{y})") 42 | 43 | def test_bounded(self): 44 | for x, y, b, t in [ 45 | ("001", "000", 1, self.assertTrue), 46 | ("000", "010", 2, self.assertTrue), 47 | ("000", "100", 1, self.assertFalse), 48 | ("000", "100", 2, self.assertTrue), 49 | ]: 50 | bo = bonesis.BoNesis(self.bn1, self.data) 51 | bo.set_constant("bounded_nonreach", b) 52 | +bo.obs(x) / +bo.obs(y) 53 | t(bo.is_satisfiable(), f"nonreach({x},{y},{b})") 54 | 55 | def test_final(self): 56 | for bn, x, y, t in [ 57 | ("bn1", "000", "111", self.assertFalse), 58 | ("bn2", "001", "111", self.assertFalse), 59 | ("bn2", "110", "000", self.assertFalse), 60 | ("bn2", "001", "000", self.assertTrue), 61 | ("bn2", "110", "111", self.assertTrue), 62 | ]: 63 | bo = bonesis.BoNesis(getattr(self, bn), self.data) 64 | +bo.obs(x) // bo.fixed(~bo.obs(y)) 65 | t(bo.is_satisfiable(), f"final_nonreach({x},{y}) [{bn}]") 66 | 67 | def test_scope(self): 68 | bn = self.bn3 69 | x = bn.zero() 70 | for scope, target, t in [ 71 | ({}, {"a": 1, "c": 0}, self.assertTrue), 72 | ({"monotone": True}, {"a": 1, "c": 0}, self.assertFalse), 73 | ({"monotone": True}, {"a": 1}, self.assertTrue), 74 | ({"max_changes": 1}, {"a": 1}, self.assertFalse), 75 | ({"max_changes": 2}, {"a": 1}, self.assertFalse), 76 | ({"max_changes": 2}, {"b": 1}, self.assertTrue), 77 | ({"max_changes": 3}, {"a": 1}, self.assertTrue), 78 | ]: 79 | bo = bonesis.BoNesis(bn) 80 | with bo.scope_reachability(**scope): 81 | ~bo.obs(x) >= ~bo.obs(target) 82 | t(bo.is_satisfiable(), 83 | f"scope_reachability({scope}) >= {target}") 84 | -------------------------------------------------------------------------------- /bonesis0/proxy_control.py: -------------------------------------------------------------------------------- 1 | # Copyright or © or Copr. Loïc Paulevé (2023) 2 | # 3 | # loic.pauleve@cnrs.fr 4 | # 5 | # This software is governed by the CeCILL license under French law and 6 | # abiding by the rules of distribution of free software. You can use, 7 | # modify and/ or redistribute the software under the terms of the CeCILL 8 | # license as circulated by CEA, CNRS and INRIA at the following URL 9 | # "http://www.cecill.info". 10 | # 11 | # As a counterpart to the access to the source code and rights to copy, 12 | # modify and redistribute granted by the license, users are provided only 13 | # with a limited warranty and the software's author, the holder of the 14 | # economic rights, and the successive licensors have only limited 15 | # liability. 16 | # 17 | # In this respect, the user's attention is drawn to the risks associated 18 | # with loading, using, modifying and/or developing or reproducing the 19 | # software by the user in light of its specific status of free software, 20 | # that may mean that it is complicated to manipulate, and that also 21 | # therefore means that it is reserved for developers and experienced 22 | # professionals having in-depth computer knowledge. Users are therefore 23 | # encouraged to load and test the software's suitability as regards their 24 | # requirements in conditions enabling the security of their systems and/or 25 | # data to be ensured and, more generally, to use and operate it in the 26 | # same conditions as regards security. 27 | # 28 | # The fact that you are presently reading this means that you have had 29 | # knowledge of the CeCILL license and that you accept its terms. 30 | # 31 | 32 | import clingo 33 | 34 | class ProxyControl(object): 35 | def __init__(self, arguments=[], **kwargs): 36 | self.cmdline_arguments = arguments 37 | self.input = "" 38 | self.control = clingo.Control(arguments, **kwargs) 39 | self.__simple = True 40 | 41 | ### 42 | 43 | @property 44 | def is_standalone_equivalent(self): 45 | return self.__simple 46 | 47 | def standalone(self, clingo_prog="clingo", custom_arguments=[], 48 | output_filename=None): 49 | content = "#!/usr/bin/env bash\n%s %s \"${@}\" - <= self.limit: 140 | print() 141 | raise StopIteration 142 | 143 | sh = setup_clingo_solve_handler(self.settings, self.control) 144 | with sh: 145 | it = setup_gil_iterator(self.settings, iter(sh), sh, self.control) 146 | model = next(it, None) 147 | if model is None: 148 | if self.__counter: 149 | print() 150 | raise StopIteration 151 | atoms = model.symbols(atoms=True) 152 | ret = self.on_model(model) 153 | 154 | self.__counter += 1 155 | now = time.time() 156 | if self.first_time is None: 157 | self.first_time = now 158 | elapsed = now-self.start_time 159 | print("\rFound {} solutions in {:.1f}s (first in {:.1f}s; rate {:.1f}s)".format( 160 | self.__counter, elapsed, 161 | self.first_time-self.start_time, 162 | elapsed/self.__counter), end="", flush=True) 163 | self.driver.on_solution(atoms) 164 | self.prepare_next(atoms) 165 | return ret 166 | -------------------------------------------------------------------------------- /bonesis/cli.py: -------------------------------------------------------------------------------- 1 | # Copyright or © or Copr. Loïc Paulevé (2023) 2 | # 3 | # loic.pauleve@cnrs.fr 4 | # 5 | # This software is governed by the CeCILL license under French law and 6 | # abiding by the rules of distribution of free software. You can use, 7 | # modify and/ or redistribute the software under the terms of the CeCILL 8 | # license as circulated by CEA, CNRS and INRIA at the following URL 9 | # "http://www.cecill.info". 10 | # 11 | # As a counterpart to the access to the source code and rights to copy, 12 | # modify and redistribute granted by the license, users are provided only 13 | # with a limited warranty and the software's author, the holder of the 14 | # economic rights, and the successive licensors have only limited 15 | # liability. 16 | # 17 | # In this respect, the user's attention is drawn to the risks associated 18 | # with loading, using, modifying and/or developing or reproducing the 19 | # software by the user in light of its specific status of free software, 20 | # that may mean that it is complicated to manipulate, and that also 21 | # therefore means that it is reserved for developers and experienced 22 | # professionals having in-depth computer knowledge. Users are therefore 23 | # encouraged to load and test the software's suitability as regards their 24 | # requirements in conditions enabling the security of their systems and/or 25 | # data to be ensured and, more generally, to use and operate it in the 26 | # same conditions as regards security. 27 | # 28 | # The fact that you are presently reading this means that you have had 29 | # knowledge of the CeCILL license and that you accept its terms. 30 | # 31 | from argparse import ArgumentParser, RawDescriptionHelpFormatter 32 | from itertools import islice 33 | import json 34 | import sys 35 | import textwrap 36 | 37 | import bonesis 38 | 39 | def json_to_bn(args): 40 | str_facts = json.load(sys.stdin) 41 | bn = bonesis.ASPModel_DNF.minibn_of_json_facts(str_facts) 42 | sys.stdout.write(bn.source()) 43 | 44 | def main_utils(): 45 | ap = ArgumentParser() 46 | sap = ap.add_subparsers(help="commands") 47 | p = sap.add_parser("json-to-bn", 48 | help="Convert json facts (stdin) to BooleanNet (stdout)") 49 | p.set_defaults(func=json_to_bn) 50 | 51 | args = ap.parse_args() 52 | if not hasattr(args, "func"): 53 | return ap.print_help() 54 | return args.func(args) 55 | 56 | def _load_domain(args): 57 | ext = args.input.lower().split(".")[-1] 58 | if ext == "bnet": 59 | dom = bonesis.BooleanNetwork(args.input) 60 | elif ext == "aeon": 61 | from bonesis.aeon import AEONDomain 62 | dom = AEONDomain.from_aeon_file(args.input, canonic=False) 63 | elif ext == "sif": 64 | dom = bonesis.InfluenceGraph.from_sif(args.input, canonic=False, 65 | unsource=False, exact=True) 66 | else: 67 | raise ValueError("Unknon file type for input") 68 | return dom 69 | 70 | def _setup_argument_domain(ap): 71 | ap.add_argument("input", 72 | help="file specifying the domain of Boolean networks (supported: .bnet, .sif, .aeon)") 73 | 74 | def main_attractors(): 75 | ap = ArgumentParser(description=textwrap.dedent("""\ 76 | This program lists the attractors (possibly restricted to fixed points) of 77 | the given Boolean network or domain of Boolean networks. 78 | 79 | The command line currently supports two types of inputs: 80 | 81 | - a single Boolean network, given in BoolNet format (.bnet extension). 82 | In that case, we recommend using mpbn instead. 83 | 84 | - a domain of Boolean networks specifed with an AEON file (.aeon extension) 85 | In that case, the union of the attractors of all the Boolean networks in 86 | that domain is returned. 87 | """), 88 | formatter_class=RawDescriptionHelpFormatter 89 | ) 90 | _setup_argument_domain(ap) 91 | ap.add_argument("--fixpoints-only", action="store_true", 92 | help="Enumerate only fixed points") 93 | ap.add_argument("--scope", default=None, 94 | help="List of nodes to display - JSON format") 95 | ap.add_argument("--limit", type=int, 96 | help="Maximum number of solutions") 97 | args = ap.parse_args() 98 | 99 | bonesis.settings["quiet"] = True 100 | 101 | scope = json.loads(args.scope) if args.scope else None 102 | 103 | dom = _load_domain(args) 104 | 105 | bo = bonesis.BoNesis(dom) 106 | x = bo.cfg() if args.fixpoints_only else bo.hypercube() 107 | bo.fixed(x) 108 | 109 | it = x.assignments(scope=scope) 110 | if args.limit: 111 | it = islice(it, args.limit) 112 | 113 | publish = print 114 | for sol in it: 115 | publish(sol) 116 | 117 | 118 | 119 | def main_reprogramming(): 120 | ap = ArgumentParser() 121 | _setup_argument_domain(ap) 122 | ap.add_argument("marker", 123 | help="Marker specification (partial configuration) - JSON format") 124 | ap.add_argument("max_size", type=int, 125 | help="Maximum number of perturbation") 126 | ap.add_argument("--reachable-from", 127 | help="Initial configuration for source-marker reprogramming - JSON format") 128 | ap.add_argument("--fixpoints", action="store_true", 129 | help="Reprogram fixed points only") 130 | ap.add_argument("--allow-no-fixpoint", action="store_true", 131 | help="When reprogramming fixed points, allow having no fixed points") 132 | ap.add_argument("--exclude", 133 | help="Perturbation blacklist - JSON format") 134 | ap.add_argument("--limit", type=int, 135 | help="Maximum number of solutions") 136 | ap.add_argument("--algorithm", default=None, 137 | help="Algorithm to use (e.g., cegar, complementary)") 138 | ap.add_argument("--verbose", action="store_true") 139 | ap.add_argument("--parallel", "-t", default=1, help="Parallel solving") 140 | args = ap.parse_args() 141 | 142 | bonesis.settings["quiet"] = not args.verbose 143 | bonesis.settings["parallel"] = args.parallel 144 | 145 | dom = _load_domain(args) 146 | 147 | M = json.loads(args.marker) 148 | k = args.max_size 149 | meth_prefix = "" 150 | meth_suffix = "" 151 | meth_args = (dom, M, k) 152 | meth_kwargs = {} 153 | if args.exclude: 154 | meth_kwargs["exclude"] = json.loads(args.exclude) 155 | if args.reachable_from: 156 | z = json.loads(args.reachable_from) 157 | meth_prefix = "source_" 158 | meth_args = (dom, z, M, k) 159 | if args.fixpoints: 160 | meth_suffix = "_fixpoints" 161 | meth_kwargs["at_least_one"] = not args.allow_no_fixpoint 162 | if args.algorithm: 163 | meth_kwargs["algorithm"] = args.algorithm 164 | 165 | from bonesis import reprogramming 166 | meth = f"{meth_prefix}marker_reprogramming{meth_suffix}" 167 | meth = getattr(reprogramming, meth) 168 | it = meth(*meth_args, **meth_kwargs) 169 | 170 | has_one = False 171 | if args.limit: 172 | it = islice(it, args.limit) 173 | for sol in it: 174 | has_one = True 175 | print(sol) 176 | if not has_one: 177 | print("No solution", file=sys.stderr) 178 | -------------------------------------------------------------------------------- /bonesis0/asp_encoding.py: -------------------------------------------------------------------------------- 1 | # Copyright or © or Copr. Loïc Paulevé (2023) 2 | # 3 | # loic.pauleve@cnrs.fr 4 | # 5 | # This software is governed by the CeCILL license under French law and 6 | # abiding by the rules of distribution of free software. You can use, 7 | # modify and/ or redistribute the software under the terms of the CeCILL 8 | # license as circulated by CEA, CNRS and INRIA at the following URL 9 | # "http://www.cecill.info". 10 | # 11 | # As a counterpart to the access to the source code and rights to copy, 12 | # modify and redistribute granted by the license, users are provided only 13 | # with a limited warranty and the software's author, the holder of the 14 | # economic rights, and the successive licensors have only limited 15 | # liability. 16 | # 17 | # In this respect, the user's attention is drawn to the risks associated 18 | # with loading, using, modifying and/or developing or reproducing the 19 | # software by the user in light of its specific status of free software, 20 | # that may mean that it is complicated to manipulate, and that also 21 | # therefore means that it is reserved for developers and experienced 22 | # professionals having in-depth computer knowledge. Users are therefore 23 | # encouraged to load and test the software's suitability as regards their 24 | # requirements in conditions enabling the security of their systems and/or 25 | # data to be ensured and, more generally, to use and operate it in the 26 | # same conditions as regards security. 27 | # 28 | # The fact that you are presently reading this means that you have had 29 | # knowledge of the CeCILL license and that you accept its terms. 30 | # 31 | 32 | import os 33 | 34 | import clingo as asp 35 | clingo_Tuple = asp.Tuple_ if hasattr(asp, "Tuple_") else asp.Tuple 36 | 37 | from scipy.special import binom 38 | 39 | from functools import reduce 40 | 41 | from mpbn import MPBooleanNetwork 42 | 43 | def py_of_symbol(symb): 44 | if symb.type is asp.SymbolType.String: 45 | return symb.string 46 | if symb.type is asp.SymbolType.Number: 47 | return symb.number 48 | if symb.type is asp.SymbolType.Function: 49 | return tuple(map(py_of_symbol, symb.arguments)) 50 | raise ValueError 51 | 52 | def symbol_of_py(obj): 53 | if isinstance(obj, str): 54 | return asp.String(obj) 55 | elif isinstance(obj, int): 56 | return asp.Number(obj) 57 | elif isinstance(obj, tuple): 58 | return clingo_Tuple([symbol_of_py(o) for o in obj]) 59 | return obj 60 | 61 | def symbols(*objs): 62 | return [symbol_of_py(obj) for obj in objs] 63 | 64 | def portfolio_path(name): 65 | return os.path.join(os.path.dirname(os.path.abspath(__file__)), 66 | f"{name}.cfg") 67 | 68 | def parse_nb_threads(opt): 69 | if opt is None: 70 | return 0 71 | if isinstance(opt, str): 72 | opt = int(opt.split(",")[0]) 73 | return opt 74 | 75 | def string_of_facts(facts): 76 | if not facts: 77 | return "" 78 | return "{}.\n".format(".\n".join(map(str,facts))) 79 | 80 | def print_facts(facts): 81 | if facts: 82 | print(string_of_facts(facts)) 83 | 84 | def nb_clauses(d): 85 | return int(binom(d, d//2)) 86 | 87 | def pkn_to_facts(pkn, maxclause=None, allow_skipping_nodes=False): 88 | facts = [] 89 | if not allow_skipping_nodes: 90 | facts.append(asp.Function("nbnode", symbols(len(pkn.nodes())))) 91 | for n in pkn.nodes(): 92 | facts.append(asp.Function("node", symbols(n))) 93 | else: 94 | facts.append("nbnode(NB) :- NB = #count{N: node(N)}") 95 | for n in pkn.nodes(): 96 | facts.append("{{{}}}".format(asp.Function("node", symbols(n)))) 97 | for (orig, dest, data) in pkn.edges(data=True): 98 | if data["sign"] in ["ukn","?","0",0]: 99 | args = symbols(orig, dest) 100 | f = "in({},{},(-1;1))".format(*args) 101 | facts.append(f) 102 | else: 103 | ds = data["sign"] 104 | if ds in ["-","+"]: 105 | ds += "1" 106 | s = int(ds) 107 | facts.append(asp.Function("in", symbols(orig, dest, s))) 108 | def bounded_nb_clauses(d): 109 | nbc = nb_clauses(d) 110 | if maxclause: 111 | nbc = min(maxclause, nbc) 112 | return nbc 113 | for n, i in pkn.in_degree(pkn.nodes()): 114 | facts.append(asp.Function("maxC", symbols(n, bounded_nb_clauses(i)))) 115 | facts += pkn.rules 116 | return facts 117 | 118 | def obs_to_facts(pstate, obsid): 119 | return [asp.Function("obs", [obsid, n, 2*v-1]) for (n,v) in pstate.items()] 120 | 121 | def sanitize_identifier(nodeid): 122 | if isinstance(nodeid, str): 123 | nodeid = nodeid.replace("-","_") 124 | else: 125 | nodeid = f"x{nodeid}" 126 | return nodeid 127 | 128 | def dnfs_of_facts(fs, ns=""): 129 | bn = {} 130 | clause_func = f"{ns}clause" 131 | constant_func = f"{ns}constant" 132 | for d in fs: 133 | if d.name == clause_func: 134 | (i,cid,lit,sign) = list(map(py_of_symbol, d.arguments)) 135 | i = sanitize_identifier(i) 136 | lit = sanitize_identifier(lit) 137 | if i not in bn: 138 | bn[i] = [] 139 | if cid > len(bn[i]): 140 | bn[i] += [set() for j in range(cid-len(bn[i]))] 141 | bn[i][cid-1].add((sign,lit)) 142 | elif d.name == constant_func and len(d.arguments) == 2: 143 | (i,v) = list(map(py_of_symbol, d.arguments)) 144 | i = sanitize_identifier(i) 145 | bn[i] = v == 1 146 | return bn 147 | 148 | def minibn_of_facts(fs, ns=""): 149 | dnfs = dnfs_of_facts(fs, ns=ns) 150 | bn = MPBooleanNetwork(auto_dnf=False) 151 | def make_lit(l): 152 | s,v=l 153 | v = bn.v(v) 154 | if s < 0: 155 | v = ~v 156 | return v 157 | def make_clause(ls): 158 | ls = list(map(make_lit, ls)) 159 | if len(ls) == 1: 160 | return ls[0] 161 | return bn.ba.AND(*ls) 162 | def make_dnf(cs): 163 | if isinstance(cs, bool): 164 | return cs 165 | cs = filter(len, cs) 166 | cs = list(map(make_clause, cs)) 167 | if len(cs) == 1: 168 | return cs[0] 169 | return bn.ba.OR(*cs) 170 | for (node, cs) in sorted(dnfs.items()): 171 | bn[node] = make_dnf(cs) 172 | return bn 173 | 174 | def configurations_of_facts(fs, pred="cfg", keys="auto"): 175 | cfgs = {} 176 | auto_keys = keys == "auto" 177 | select = [] if auto_keys else (None if keys == "all" else keys) 178 | for a in fs: 179 | if a.name != pred: 180 | continue 181 | arity = len(a.arguments) 182 | if arity == 1 and auto_keys: 183 | select.append(py_of_symbol(a.arguments[0])) 184 | continue 185 | if arity != 3: 186 | continue 187 | cid, n, v = a.arguments 188 | n = py_of_symbol(n) 189 | v = v.number 190 | cid = py_of_symbol(cid) 191 | if cid not in cfgs: 192 | cfgs[cid] = {} 193 | cfgs[cid][n] = max(v, 0) 194 | if select is not None: 195 | return {k: cfgs[k] for k in select} 196 | return cfgs 197 | -------------------------------------------------------------------------------- /bonesis/manager.py: -------------------------------------------------------------------------------- 1 | # Copyright or © or Copr. Loïc Paulevé (2023) 2 | # 3 | # loic.pauleve@cnrs.fr 4 | # 5 | # This software is governed by the CeCILL license under French law and 6 | # abiding by the rules of distribution of free software. You can use, 7 | # modify and/ or redistribute the software under the terms of the CeCILL 8 | # license as circulated by CEA, CNRS and INRIA at the following URL 9 | # "http://www.cecill.info". 10 | # 11 | # As a counterpart to the access to the source code and rights to copy, 12 | # modify and redistribute granted by the license, users are provided only 13 | # with a limited warranty and the software's author, the holder of the 14 | # economic rights, and the successive licensors have only limited 15 | # liability. 16 | # 17 | # In this respect, the user's attention is drawn to the risks associated 18 | # with loading, using, modifying and/or developing or reproducing the 19 | # software by the user in light of its specific status of free software, 20 | # that may mean that it is complicated to manipulate, and that also 21 | # therefore means that it is reserved for developers and experienced 22 | # professionals having in-depth computer knowledge. Users are therefore 23 | # encouraged to load and test the software's suitability as regards their 24 | # requirements in conditions enabling the security of their systems and/or 25 | # data to be ensured and, more generally, to use and operate it in the 26 | # same conditions as regards security. 27 | # 28 | # The fact that you are presently reading this means that you have had 29 | # knowledge of the CeCILL license and that you accept its terms. 30 | # 31 | 32 | import copy 33 | 34 | import clingo 35 | 36 | from .language import BonesisTerm, Some 37 | 38 | class BonesisManager(object): 39 | def __init__(self, bo): 40 | self.bo = bo 41 | self.properties = [] 42 | self.observations = set() 43 | self.anon_observations = {} 44 | self.configurations = set() 45 | self.hypercubes = set() 46 | self.some = {} 47 | self.optimizations = [] 48 | 49 | def reset_from(self, m2): 50 | for attr in ["properties", 51 | "observations", 52 | "anon_observations", 53 | "configurations", 54 | "hypercubes", 55 | "some", 56 | "optimizations"]: 57 | setattr(self, attr, copy.copy(getattr(m2, attr))) 58 | 59 | def assert_node_exists(self, node, assertion=KeyError): 60 | if not node in self.bo.domain: 61 | raise assertion(node) 62 | 63 | def push(self, rule): 64 | self.properties.append(rule) 65 | 66 | def push_term(self, name, *args, **kwargs): 67 | self.push((name, args, kwargs)) 68 | 69 | def register_observation(self, obs): 70 | if obs.name is None and hasattr(obs, "data"): 71 | key = tuple(sorted(obs.data.items())) 72 | name = self.anon_observations.get(key, None) 73 | if name is None: 74 | name = f"_obs{len(self.anon_observations)}" 75 | self.anon_observations[key] = name 76 | obs.name = name 77 | elif not obs.name in self.bo.data: 78 | raise ValueError(f"No data registered at key {repr(obs.name)}") 79 | if obs.name not in self.observations: 80 | self.observations.add(obs.name) 81 | if hasattr(obs, "data"): 82 | self.push_term("obs_data", obs.name, obs.data) 83 | else: 84 | self.push_term("obs", obs.name) 85 | 86 | def register_configuration(self, cfg): 87 | if cfg.name is None: 88 | if cfg.obs: 89 | i = 0 90 | cfg.name = (cfg.obs.name, i) 91 | while cfg.name in self.configurations: 92 | i += 1 93 | cfg.name = (cfg.obs.name, i) 94 | else: 95 | cfg.name = f"__cfg{len(self.configurations)}" 96 | if cfg.name not in self.configurations: 97 | self.configurations.add(cfg.name) 98 | self.push_term("cfg", cfg.name) 99 | if cfg.obs: 100 | self.register_predicate("bind_cfg", cfg.name, cfg.obs.name) 101 | 102 | def register_hypercube(self, h): 103 | name = f"_h{len(self.hypercubes)}" 104 | h.name = name 105 | self.push_term("hypercube", h) 106 | if h.obs: 107 | self.register_predicate("bind_hypercube", name, h.obs.name) 108 | 109 | def register_predicate(self, name, *args, **kwargs): 110 | def validate_mgr(mgr): 111 | while hasattr(mgr, "parent"): 112 | if mgr is self: 113 | return 114 | mgr = mgr.parent 115 | assert mgr is self, "mixed managers" 116 | for obj in args: 117 | if isinstance(obj, BonesisTerm): 118 | validate_mgr(obj.mgr) 119 | self.push_term(name, *args, **kwargs) 120 | 121 | def register_some(self, some): 122 | if some.name is None: 123 | some.name = f"__some{len(self.some)}" 124 | assert some.name not in self.some, "Duplicate Some identifier" 125 | self.some[some.name] = some 126 | self.push_term("some", some) 127 | 128 | def append_optimization(self, opt, name): 129 | self.optimizations.append((opt, name)) 130 | 131 | def mutant_context(self, *args, **kwargs): 132 | return _MutantManager(self, *args, **kwargs) 133 | 134 | def scope_reachability_context(self, *a, **k): 135 | return _ReachabilityScopeManager(self, *a, **k) 136 | 137 | 138 | class _MutantManager(BonesisManager): 139 | _mutant_id = 0 140 | def __init__(self, parent, mutations, weak=False): 141 | for prop in ["bo", "properties", 142 | "observations", "anon_observations", 143 | "configurations", "some"]: 144 | setattr(self, prop, getattr(parent, prop)) 145 | self.parent = parent 146 | self.managed_configurations = set() 147 | self._mutations = mutations 148 | self.mutations = self.get_mutations() 149 | self.__class__._mutant_id += 1 150 | self.mutant_name = self.__class__._mutant_id 151 | self.push_term("mutant", self.mutant_name, self.mutations) 152 | if weak: 153 | self.push_term("weak_mutant", self.mutant_name, self._mutations) 154 | 155 | def get_mutations(self): 156 | m = self._mutations.copy() 157 | if hasattr(self.parent, "mutations"): 158 | m.update(self.parent.mutations) 159 | return m 160 | 161 | def register_predicate(self, name, *args, **kwargs): 162 | self.parent.register_predicate(name, *args, **kwargs, 163 | mutant=self.mutant_name) 164 | 165 | class _ReachabilityScopeManager(BonesisManager): 166 | def __init__(self, parent, options): 167 | for prop in ["bo", "properties", 168 | "observations", "anon_observations", 169 | "configurations", "some"]: 170 | setattr(self, prop, getattr(parent, prop)) 171 | self.parent = parent 172 | self.__options = options 173 | def register_predicate(self, name, *args, **kwargs): 174 | if name in ["bind_cfg", 175 | "different", 176 | "fixpoint"] or name.startswith("cfg_"): 177 | return super().register_predicate(name, *args, **kwargs) 178 | if name not in ["reach"]: 179 | raise TypeError(f"Unsupported predicate {name} in scoped reachability") 180 | kwargs = self.__options | kwargs 181 | self.parent.register_predicate(name, *args, **kwargs) 182 | -------------------------------------------------------------------------------- /bonesis/domains.py: -------------------------------------------------------------------------------- 1 | # Copyright or © or Copr. Loïc Paulevé (2023) 2 | # 3 | # loic.pauleve@cnrs.fr 4 | # 5 | # This software is governed by the CeCILL license under French law and 6 | # abiding by the rules of distribution of free software. You can use, 7 | # modify and/ or redistribute the software under the terms of the CeCILL 8 | # license as circulated by CEA, CNRS and INRIA at the following URL 9 | # "http://www.cecill.info". 10 | # 11 | # As a counterpart to the access to the source code and rights to copy, 12 | # modify and redistribute granted by the license, users are provided only 13 | # with a limited warranty and the software's author, the holder of the 14 | # economic rights, and the successive licensors have only limited 15 | # liability. 16 | # 17 | # In this respect, the user's attention is drawn to the risks associated 18 | # with loading, using, modifying and/or developing or reproducing the 19 | # software by the user in light of its specific status of free software, 20 | # that may mean that it is complicated to manipulate, and that also 21 | # therefore means that it is reserved for developers and experienced 22 | # professionals having in-depth computer knowledge. Users are therefore 23 | # encouraged to load and test the software's suitability as regards their 24 | # requirements in conditions enabling the security of their systems and/or 25 | # data to be ensured and, more generally, to use and operate it in the 26 | # same conditions as regards security. 27 | # 28 | # The fact that you are presently reading this means that you have had 29 | # knowledge of the CeCILL license and that you accept its terms. 30 | # 31 | 32 | from zipfile import ZipFile 33 | import os 34 | import tempfile 35 | 36 | from boolean import boolean 37 | from colomoto import minibn 38 | from colomoto_jupyter import import_colomoto_tool 39 | import mpbn 40 | from numpy.random import choice 41 | import networkx as nx 42 | import pandas as pd 43 | 44 | class BonesisDomain(object): 45 | pass 46 | 47 | class BooleanNetwork(mpbn.MPBooleanNetwork, BonesisDomain): 48 | pass 49 | 50 | class BooleanNetworksEnsemble(BonesisDomain, list): 51 | def __init__(self, bns=None): 52 | super().__init__(bns if bns is not None else []) 53 | 54 | @classmethod 55 | def from_zip(celf, zipfile, ensure_wellformed=False): 56 | bns = celf() 57 | make_bn = BooleanNetwork if ensure_wellformed else minibn.BooleanNetwork 58 | with ZipFile(zipfile, "r") as bundle: 59 | for entry in bundle.infolist(): 60 | if entry.is_dir() or not \ 61 | entry.filename.lower().endswith(".bnet"): 62 | continue 63 | with bundle.open(entry) as fp: 64 | bns.append(make_bn(fp.read().decode())) 65 | return bns 66 | 67 | 68 | 69 | label_map = { 70 | 1: "+", 71 | -1: "-", 72 | 0: "?" 73 | } 74 | sign_map = { 75 | 1: 1, 76 | -1: -1, 77 | "->": 1, 78 | "-|": -1, 79 | "+": 1, 80 | "-": -1, 81 | "+1": 1, 82 | "-1": -1, 83 | "ukn": 0, 84 | "?": 0, 85 | "unspecified": 0, 86 | } 87 | def sign_of_label(label): 88 | if label in sign_map: 89 | return sign_map[label] 90 | label = label.lower() 91 | if label.startswith("act") or label.startswith("stim"): 92 | return 1 93 | if label.startswith("inh"): 94 | return -1 95 | raise ValueError(label) 96 | 97 | class InfluenceGraph(BonesisDomain, nx.MultiDiGraph): 98 | _options = ( 99 | "allow_skipping_nodes", 100 | "canonic", 101 | "exact", 102 | "maxclause", 103 | ) 104 | def __init__(self, graph=None, 105 | maxclause=None, 106 | allow_skipping_nodes=False, 107 | canonic=True, 108 | exact=False, 109 | autolabel=True): 110 | nx.MultiDiGraph.__init__(self, graph) 111 | # TODO: ensures graph is well-formed 112 | self.maxclause = maxclause 113 | self.allow_skipping_nodes = allow_skipping_nodes 114 | self.canonic = canonic 115 | self.exact = exact 116 | self.rules = [] 117 | if autolabel: 118 | for a, b, data in self.edges(data=True): 119 | if "label" not in data: 120 | l = label_map.get(data.get("sign")) 121 | if l: 122 | data["label"] = l 123 | 124 | def sources(self): 125 | return set([n for n,i in self.in_degree(self.nodes()) if i == 0]) 126 | def unsource(self): 127 | self.add_edges_from([(n, n, {"sign": 1, "label": "+"}) for n in self.sources()]) 128 | 129 | def max_indegree(self): 130 | return max(dict(self.in_degree()).values()) 131 | 132 | @property 133 | def options(self): 134 | return {opt: getattr(self, opt) for opt in self._options} 135 | 136 | def subgraph(self, *args, **kwargs): 137 | g = super().subgraph(*args, **kwargs) 138 | return self.__class__(g, **self.options) 139 | 140 | @classmethod 141 | def from_csv(celf, filename, column_source=0, column_target=1, column_sign=2, 142 | sep=",", 143 | unsource=True, **kwargs): 144 | df = pd.read_csv(filename, sep=sep) 145 | def get_colname(spec): 146 | return df.columns[spec] if isinstance(spec, int) else spec 147 | column_source = get_colname(column_source) 148 | column_target = get_colname(column_target) 149 | column_sign = get_colname(column_sign) 150 | df.rename(columns = { 151 | column_source: "in", 152 | column_target: "out", 153 | column_sign: "sign"}, inplace=True) 154 | df["sign"] = df["sign"].map(sign_of_label) 155 | g = nx.from_pandas_edgelist(df, "in", "out", ["sign"], nx.MultiDiGraph()) 156 | pkn = celf(g, **kwargs) 157 | if unsource: 158 | pkn.unsource() 159 | return pkn 160 | 161 | @classmethod 162 | def from_sif(celf, filename, sep="\\s+", unsource=True, **kwargs): 163 | df = pd.read_csv(filename, header=None, 164 | names=("in", "sign", "out"), sep=sep) 165 | df["sign"] = df["sign"].map(sign_of_label) 166 | g = nx.from_pandas_edgelist(df, "in", "out", ["sign"], nx.MultiDiGraph()) 167 | pkn = celf(g, **kwargs) 168 | if unsource: 169 | pkn.unsource() 170 | return pkn 171 | 172 | @classmethod 173 | def from_ginsim(celf, lrg, **kwargs): 174 | ginsim = import_colomoto_tool("ginsim") 175 | fd, filename = tempfile.mkstemp(".sif") 176 | os.close(fd) 177 | try: 178 | ginsim.service("reggraph").export(lrg, filename) 179 | pkn = celf.from_sif(filename, **kwargs) 180 | finally: 181 | os.unlink(filename) 182 | return pkn 183 | 184 | @classmethod 185 | def complete(celf, n, sign=0, loops=True, **kwargs): 186 | g = nx.complete_graph(n, nx.DiGraph) 187 | for e in g.edges(data=True): 188 | e[2]["sign"] = sign 189 | if loops: 190 | for i in g: 191 | g.add_edge(i, i, sign=sign) 192 | return celf(g, **kwargs) 193 | 194 | @classmethod 195 | def all_on_one(celf, n, sign=1, **kwargs): 196 | g = nx.DiGraph() 197 | for i in range(n): 198 | g.add_edge(i, 0, sign=sign) 199 | return celf(g, **kwargs) 200 | 201 | @classmethod 202 | def scale_free(celf, n, p_pos=0.6, unsource=True, **kwargs): 203 | scale_free_kwargs = dict([(k,v) for (k,v) in kwargs.items() \ 204 | if k not in celf._options]) 205 | celf_kwargs = dict([(k,v) for (k,v) in kwargs.items() \ 206 | if k in celf._options]) 207 | g = nx.DiGraph(nx.scale_free_graph(n, **scale_free_kwargs)) 208 | signs = choice([-1,1], size=len(g.edges()), p=[1-p_pos,p_pos]) 209 | for j, e in enumerate(g.edges(data=True)): 210 | e[2]["sign"] = signs[j] 211 | pkn = celf(g, **celf_kwargs) 212 | if unsource: 213 | pkn.unsource() 214 | return pkn 215 | -------------------------------------------------------------------------------- /bonesis/reprogramming.py: -------------------------------------------------------------------------------- 1 | # Copyright or © or Copr. Loïc Paulevé (2023) 2 | # 3 | # loic.pauleve@cnrs.fr 4 | # 5 | # This software is governed by the CeCILL license under French law and 6 | # abiding by the rules of distribution of free software. You can use, 7 | # modify and/ or redistribute the software under the terms of the CeCILL 8 | # license as circulated by CEA, CNRS and INRIA at the following URL 9 | # "http://www.cecill.info". 10 | # 11 | # As a counterpart to the access to the source code and rights to copy, 12 | # modify and redistribute granted by the license, users are provided only 13 | # with a limited warranty and the software's author, the holder of the 14 | # economic rights, and the successive licensors have only limited 15 | # liability. 16 | # 17 | # In this respect, the user's attention is drawn to the risks associated 18 | # with loading, using, modifying and/or developing or reproducing the 19 | # software by the user in light of its specific status of free software, 20 | # that may mean that it is complicated to manipulate, and that also 21 | # therefore means that it is reserved for developers and experienced 22 | # professionals having in-depth computer knowledge. Users are therefore 23 | # encouraged to load and test the software's suitability as regards their 24 | # requirements in conditions enabling the security of their systems and/or 25 | # data to be ensured and, more generally, to use and operate it in the 26 | # same conditions as regards security. 27 | # 28 | # The fact that you are presently reading this means that you have had 29 | # knowledge of the CeCILL license and that you accept its terms. 30 | # 31 | 32 | 33 | from colomoto import minibn 34 | 35 | import networkx as nx 36 | from functools import reduce 37 | 38 | import bonesis 39 | from bonesis0.asp_encoding import symbol_of_py 40 | 41 | def prune_domain_for_marker(f, M): 42 | def get_doi(g): 43 | return reduce(set.union, (nx.ancestors(g, m) for m in M), set(M)) 44 | if isinstance(f, minibn.BooleanNetwork): 45 | keep = get_doi(f.influence_graph()) 46 | return f.__class__({i: f[i] for i in keep}) 47 | else: 48 | return f.subgraph(get_doi(f)) 49 | 50 | def marker_reprogramming_fixpoints(f, M, k, at_least_one=True, **some_opts): 51 | bo = bonesis.BoNesis(f) 52 | control = bo.Some(max_size=k, **some_opts) 53 | with bo.mutant(control): 54 | if at_least_one: 55 | bo.fixed(~bo.obs(M)) 56 | bo.all_fixpoints(bo.obs(M)) 57 | return control.assignments() 58 | 59 | def source_marker_reprogramming_fixpoints(f, z, M, k, at_least_one=True, 60 | **some_opts): 61 | bo = bonesis.BoNesis(f) 62 | control = bo.Some(max_size=k, **some_opts) 63 | with bo.mutant(control): 64 | if at_least_one: 65 | ~bo.obs(z) >= bo.fixed(~bo.obs(M)) 66 | ~bo.obs(z) >> "fixpoints" ^ {bo.obs(M)} 67 | return control.assignments() 68 | 69 | def trapspace_reprogramming(f, M, k, algorithm="cegar", **some_opts): 70 | """ 71 | Marker/Trapspace reprogramming of BN domain `f`. 72 | 73 | Returns mutations which ensure that all the minimal trap spaces match with 74 | the given marker `M`. 75 | """ 76 | f = prune_domain_for_marker(f, M) 77 | if algorithm == "cegar": 78 | meth = _trapspace_reprogramming_cegar 79 | else: 80 | meth = _trapspace_reprogramming_complementary 81 | return meth(f, M, k, **some_opts) 82 | 83 | marker_reprogramming = trapspace_reprogramming 84 | 85 | 86 | def _trapspace_reprogramming_complementary(f, M, k, **some_opts): 87 | bo = bonesis.BoNesis(f) 88 | bad_control = bo.Some(max_size=k, **some_opts) 89 | with bo.mutant(bad_control): 90 | x = bo.cfg() 91 | bo.in_attractor(x) != bo.obs(M) 92 | return bad_control.complementary_assignments() 93 | 94 | def source_marker_reprogramming(f, z, M, k, **some_opts): 95 | f = prune_domain_for_marker(f, M) 96 | bo = bonesis.BoNesis(f) 97 | bad_control = bo.Some(max_size=k, **some_opts) 98 | with bo.mutant(bad_control): 99 | x = bo.cfg() 100 | ~bo.obs(z) >= bo.in_attractor(x) != bo.obs(M) 101 | return bad_control.complementary_assignments() 102 | 103 | 104 | ### 105 | ### CEGAR-based implementations 106 | ### 107 | 108 | from bonesis.views import SomeView 109 | 110 | def _trapspace_reprogramming_cegar(dom, M, k, **some_opts): 111 | data = {"M": M} 112 | 113 | bo = bonesis.BoNesis(dom, data) 114 | P = bo.Some(max_size=k, **some_opts) 115 | P_id = symbol_of_py(P.name) 116 | with bo.mutant(P): 117 | # there exists at least one minimal trap spaces matching M 118 | bo.fixed(bo.obs("M")) 119 | 120 | class BoCegar(SomeView): 121 | single_shot = False 122 | project = False 123 | ice = 0 124 | def __iter__(self): 125 | self.configure() 126 | return self 127 | 128 | def __next__(self): 129 | while True: 130 | with self.control.solve(yield_=True) as candidates: 131 | found_P = False 132 | for candidate in candidates: 133 | found_P = True 134 | P = self.parse_model(candidate) 135 | p = candidate.symbols(shown=True) 136 | 137 | ## check candidate 138 | boc = bonesis.BoNesis(dom, data) 139 | with boc.mutant(P): 140 | x = boc.cfg() 141 | boc.in_attractor(x) 142 | x != boc.obs("M") 143 | view = x.assignments(limit=1) 144 | view.configure() 145 | if (ce := next(iter(view.control.solve(yield_=True)), None)) is not None: 146 | self.ice += 1 147 | x = [a for a in ce.symbols(shown=True) 148 | if a.name == "cfg" and a.arguments[0].string == "__cfg0"] 149 | break 150 | 151 | if not found_P: 152 | raise StopIteration 153 | 154 | if ce is None: 155 | inject = f":- {','.join(map(str, p))}." 156 | self.control.add("skip", [], inject) 157 | self.control.ground([("skip", [])]) 158 | return P 159 | ns = f"_ce{self.ice}_" 160 | cets = f"{ns}ts" 161 | fixts = f"{ns}fix" 162 | inject = [f"mcfg({cets},{a.arguments[1]},{a.arguments[2]})." for a in x] 163 | if not isinstance(dom, minibn.BooleanNetwork): 164 | conds = list(map(str,p)) + [f"#count {{ N : some_freeze({P_id},N,_) }} {len(p)}"] 165 | inject.append(f":- {','.join(conds)}.") 166 | inject += [ 167 | # compute trap space of counter example 168 | f"mcfg({cets},N,V) :- {ns}eval({cets},N,V).", 169 | f"clamped({cets},N,V) :- mutant(_,N,V).", 170 | # choose sub-space in it 171 | f"1 {{mcfg({fixts},N,V):mcfg({cets},N,V)}} :- mcfg({cets},N,_).", 172 | # saturate it 173 | f"mcfg({fixts},N,V) :- {ns}eval({fixts},N,V).", 174 | f"clamped({fixts},N,V) :- clamped({cets},N,V).", 175 | 176 | f"{ns}eval(X,N,C,-1) :- clause(N,C,L,-V), mcfg(X,L,V), not clamped(X,N,_).", 177 | f"{ns}eval(X,N,C,1) :- mcfg(X,L,V): clause(N,C,L,V); clause(N,C,_,_), mcfg(X,_,_), not clamped(X,N,_).", 178 | f"{ns}eval(X,N,1) :- {ns}eval(X,N,C,1), clause(N,C,_,_).", 179 | f"{ns}eval(X,N,-1) :- {ns}eval(X,N,C,-1): clause(N,C,_,_); clause(N,_,_,_), mcfg(X,_,_).", 180 | f"{ns}eval(X,N,V) :- clamped(X,N,V).", 181 | f"{ns}eval(X,N,V) :- constant(N,V), mcfg(X,_,_), not clamped(X,N,_).", 182 | 183 | f"{ns}eval(X,N,V) :- {ns}evalbdd(X,N,V), node(N), not clamped(X,N,_).", 184 | f"{ns}evalbdd(X,V,V) :- mcfg(X,_,_), V=(-1;1).", 185 | f"{ns}evalbdd(X,B,V) :- bdd(B,N,_,HI), mcfg(X,N,1), {ns}evalbdd(X,HI,V).", 186 | f"{ns}evalbdd(X,B,V) :- bdd(B,N,LO,_), mcfg(X,N,-1), {ns}evalbdd(X,LO,V).", 187 | f"{ns}evalbdd(X,B,V) :- mcfg(X,_,_), bdd(B,V).", 188 | ] 189 | # TS(y) must match with M 190 | inject.append(f":- obs(\"M\",N,V), mcfg({fixts},N,-V).") 191 | inject = "\n".join(inject) 192 | self.control.add(f"cegar{ns}", [], inject) 193 | self.control.ground([(f"cegar{ns}", [])]) 194 | 195 | return BoCegar(P, bo, solutions="subset-minimal") 196 | -------------------------------------------------------------------------------- /bonesis/aeon.py: -------------------------------------------------------------------------------- 1 | # Copyright or © or Copr. Loïc Paulevé (2023) 2 | # 3 | # loic.pauleve@cnrs.fr 4 | # 5 | # This software is governed by the CeCILL license under French law and 6 | # abiding by the rules of distribution of free software. You can use, 7 | # modify and/ or redistribute the software under the terms of the CeCILL 8 | # license as circulated by CEA, CNRS and INRIA at the following URL 9 | # "http://www.cecill.info". 10 | # 11 | # As a counterpart to the access to the source code and rights to copy, 12 | # modify and redistribute granted by the license, users are provided only 13 | # with a limited warranty and the software's author, the holder of the 14 | # economic rights, and the successive licensors have only limited 15 | # liability. 16 | # 17 | # In this respect, the user's attention is drawn to the risks associated 18 | # with loading, using, modifying and/or developing or reproducing the 19 | # software by the user in light of its specific status of free software, 20 | # that may mean that it is complicated to manipulate, and that also 21 | # therefore means that it is reserved for developers and experienced 22 | # professionals having in-depth computer knowledge. Users are therefore 23 | # encouraged to load and test the software's suitability as regards their 24 | # requirements in conditions enabling the security of their systems and/or 25 | # data to be ensured and, more generally, to use and operate it in the 26 | # same conditions as regards security. 27 | # 28 | # The fact that you are presently reading this means that you have had 29 | # knowledge of the CeCILL license and that you accept its terms. 30 | # 31 | 32 | import itertools 33 | import re 34 | 35 | import boolean 36 | import clingo 37 | 38 | from colomoto.minibn import struct_of_dnf, NOT 39 | 40 | import mpbn 41 | import bonesis 42 | from bonesis.domains import BonesisDomain 43 | from bonesis0.asp_encoding import nb_clauses, symbols, minibn_of_facts 44 | from bonesis.asp_encoding import s2v 45 | 46 | RE_PARAMETER = re.compile(r'(\w+)\(([^\)]*)\)') 47 | 48 | class AEONFunction(object): 49 | def __init__(self, func, struct): 50 | self.func = func 51 | self.struct = struct 52 | def __str__(self): 53 | return str(self.func) 54 | def __repr__(self): 55 | return str(self.func) 56 | 57 | class AEONPartialFunction(object): 58 | def __init__(self, func, struct, params): 59 | self.func = func 60 | self.struct = struct 61 | self.params = params 62 | def __str__(self): 63 | return f"{self.func} [{self.params}]" 64 | def __repr__(self): 65 | return f"AEONPartialFunction({self.func},{self.params})" 66 | 67 | 68 | def asp_of_AEONReg(dom, boenc, n, acting_n=None, regs=None, ns=""): 69 | regs = dom.am.predecessors(n) if regs is None else regs 70 | acting_n = n if acting_n is None else acting_n 71 | d = len(regs) 72 | boenc.load_template_domain(ns=ns, allow_externals=ns) 73 | if dom.canonic: 74 | boenc.load_template_canonic(ns=ns) 75 | rules = [] 76 | nbc = dom.get_maxclause(d) 77 | rules.append(clingo.Function(f"{ns}maxC", symbols(n, nbc))) 78 | for m in regs: 79 | reg = dom.am.find_regulation(m, acting_n) 80 | m = dom.am.get_variable_name(m) 81 | args = symbols(m, n) 82 | monotonicity = reg.get("sign") 83 | if monotonicity in [True, "positive", "+"]: 84 | sign = 1 85 | elif monotonicity in [False, "negative", "-"]: 86 | sign = -1 87 | else: 88 | sign = "(-1;1)" 89 | rules.append("{}in({},{},{})".format(ns, *args, sign)) 90 | if reg.get("essential"): 91 | boenc.load_template_edge(ns=ns) 92 | rules.append(":- not {}edge({},{},_)".format(ns, *args)) 93 | return rules 94 | 95 | def asp_of_AEONFunction(dom, n, func): 96 | rules = [] 97 | if isinstance(func.struct, bool): 98 | rules.append(clingo.Function("constant", symbols(n, s2v(func.struct)))) 99 | return rules 100 | for cid, c in enumerate(func.struct): 101 | if isinstance(c, bool): 102 | rules.append(clingo.Function("constant", symbols(n, s2v(c)))) 103 | else: 104 | for m, s in c: 105 | rules.append(clingo.Function("clause", symbols(n, cid+1, m, s2v(s)))) 106 | return rules 107 | 108 | def asp_of_AEONParameters(dom, boenc): 109 | ns = "p_" 110 | rules = [] 111 | for param_name, param_args in dom.params.items(): 112 | # TODO: we make the assumption that the regulations are the same accross 113 | # nodes using the parameter; this should be either explicitely checked, 114 | # or this assumption should be removed 115 | rules.append(clingo.Function(f"{ns}node", symbols(param_name))) 116 | n = next(iter(dom.param_nodes[param_name])) 117 | rules += asp_of_AEONReg(dom, boenc, n=param_name, acting_n=n, regs=param_args, ns=ns) 118 | return rules 119 | 120 | def asp_of_AEONPartialFunction(dom, n, func): 121 | rules = [] 122 | for cid, c in enumerate(func.struct): 123 | cid = cid + 1 124 | ps = tuple(set([v for (v,_) in c if v in func.params])) 125 | if not ps: 126 | for m, s in c: 127 | rules.append(clingo.Function("clause", symbols(n, cid, m, s2v(s)))) 128 | else: 129 | for m, s in c: 130 | if m in func.params: 131 | if s < 0: 132 | raise NotImplementedError("negation of parameters is not supported yet") 133 | continue 134 | rules.append(clingo.Function(f"pre_clause", symbols(n, cid, m, s2v(s)))) 135 | 136 | ps = symbols(*ps) 137 | cases = [[(f"p_clause({p},C{i},M{i},S{i})", i), 138 | (f"p_constant({p}, 1),C{i}=c", -1)] for i, p in enumerate(ps)] 139 | gid = ",".join([f"C{i}" for i in range(len(ps))]) 140 | n, cid = symbols(n, cid) 141 | for glue in itertools.product(*cases): 142 | glue = dict(glue) 143 | contents = set(glue.values()) - {-1} 144 | glue = "; ".join(glue) 145 | for i in contents: 146 | rules.append(f"clause({n},({cid},{gid}),M{i},S{i}) :- node({n}); {glue}") 147 | rules.append(f"clause({n},({cid},{gid}),M,S) :- pre_clause({n},{cid},M,S), clause({n},({cid},{gid}),_,_)") 148 | all_constants = "; ".join([c[1][0] for c in cases]) 149 | rules.append(f"clause({n},({cid},{gid}),M,S) :- pre_clause({n},{cid},M,S), {all_constants}") 150 | return rules 151 | 152 | def asp_of_AEONDomain(dom, boenc): 153 | rules = [] 154 | rules.append(clingo.Function("nbnode", symbols(len(dom)))) 155 | rules.extend(asp_of_AEONParameters(dom, boenc)) 156 | for name, func in dom.items(): 157 | rules.append(clingo.Function("node", symbols(name))) 158 | if func is None: 159 | rules.extend(asp_of_AEONReg(dom, boenc, name)) 160 | elif isinstance(func, AEONFunction): 161 | rules.extend(asp_of_AEONFunction(dom, name, func)) 162 | elif isinstance(func, AEONPartialFunction): 163 | rules.extend(asp_of_AEONPartialFunction(dom, name, func)) 164 | else: 165 | raise TypeError() 166 | return rules 167 | 168 | class AEONDomain(BonesisDomain, dict): 169 | bonesis_encoder = asp_of_AEONDomain 170 | def __init__(self, aeon_model, maxclause=None, canonic=True): 171 | super().__init__() 172 | self.am = aeon_model 173 | self.ba = boolean.BooleanAlgebra(NOT_class=NOT) 174 | self.maxclause = maxclause 175 | self.canonic = canonic # canonicty is ensured only for parameters and free functions 176 | self.params = {} 177 | self.param_nodes = {} 178 | self._f = bonesis.BooleanNetwork({}) 179 | 180 | for i in self.am.variables(): 181 | name = self.am.get_variable_name(i) 182 | func = self.am.get_update_function(i) 183 | self[name] = func 184 | 185 | def get_maxclause(self, d): 186 | if d == 0: 187 | return 0 188 | nbc = nb_clauses(d) 189 | if self.maxclause: 190 | nbc = min(self.maxclause, nbc) 191 | return nbc 192 | 193 | def __setitem__(self, node, func): 194 | if func is None: 195 | return super().__setitem__(node, func) 196 | func_params = set() 197 | def register_parameter(g): 198 | name = g.group(1) 199 | args = tuple(g.group(2).replace(" ","").split(",")) 200 | if name not in self.params: 201 | self.params[name] = args 202 | func_params.add(name) 203 | else: 204 | assert self.params[name] == args 205 | return name 206 | func = str(func) if not isinstance(func, str) else func 207 | f = self.ba.parse(RE_PARAMETER.sub(register_parameter, func)) 208 | self._f[node] = f 209 | f = self._f[node] 210 | s = struct_of_dnf(self.ba, f, list, sort=True) 211 | if func_params: 212 | func = AEONPartialFunction(f, s, 213 | {p: self.params[p] for p in func_params}) 214 | for name in func_params: 215 | if name in self.param_nodes: 216 | self.param_nodes[name].add(node) 217 | else: 218 | self.param_nodes[name] = {node} 219 | else: 220 | func = AEONFunction(f, s) 221 | return super().__setitem__(node, func) 222 | 223 | @classmethod 224 | def from_aeon_file(celf, filename, **opts): 225 | import biodivine_aeon 226 | with open(filename) as fp: 227 | data = fp.read() 228 | am = biodivine_aeon.BooleanNetwork.from_aeon(data) 229 | return celf(am, **opts) 230 | 231 | 232 | class AEONParametersView(bonesis.BonesisView): 233 | project = True 234 | show_templates = ["boolean_network"] 235 | ns = "p_" 236 | def configure_show(self): 237 | for tpl in self.show_templates: 238 | for x in self.aspmodel.show[tpl]: 239 | self.control.add("base", [], f"#show {self.ns}{x}.") 240 | def format_model(self, model): 241 | atoms = model.symbols(shown=True) 242 | return minibn_of_facts(atoms, ns=self.ns) 243 | 244 | __all__ = ["AEONDomain", "AEONParametersView"] 245 | 246 | if __name__ == "__main__": 247 | import sys 248 | dom = AEONDomain.from_aeon_file(sys.argv[1]) 249 | bo = bonesis.BoNesis(dom) 250 | bo.aspmodel.make() 251 | print(str(bo.aspmodel)) 252 | -------------------------------------------------------------------------------- /bonesis/language.py: -------------------------------------------------------------------------------- 1 | # Copyright or © or Copr. Loïc Paulevé (2023) 2 | # 3 | # loic.pauleve@cnrs.fr 4 | # 5 | # This software is governed by the CeCILL license under French law and 6 | # abiding by the rules of distribution of free software. You can use, 7 | # modify and/ or redistribute the software under the terms of the CeCILL 8 | # license as circulated by CEA, CNRS and INRIA at the following URL 9 | # "http://www.cecill.info". 10 | # 11 | # As a counterpart to the access to the source code and rights to copy, 12 | # modify and redistribute granted by the license, users are provided only 13 | # with a limited warranty and the software's author, the holder of the 14 | # economic rights, and the successive licensors have only limited 15 | # liability. 16 | # 17 | # In this respect, the user's attention is drawn to the risks associated 18 | # with loading, using, modifying and/or developing or reproducing the 19 | # software by the user in light of its specific status of free software, 20 | # that may mean that it is complicated to manipulate, and that also 21 | # therefore means that it is reserved for developers and experienced 22 | # professionals having in-depth computer knowledge. Users are therefore 23 | # encouraged to load and test the software's suitability as regards their 24 | # requirements in conditions enabling the security of their systems and/or 25 | # data to be ensured and, more generally, to use and operate it in the 26 | # same conditions as regards security. 27 | # 28 | # The fact that you are presently reading this means that you have had 29 | # knowledge of the CeCILL license and that you accept its terms. 30 | # 31 | 32 | __language_api__ = {} 33 | 34 | def language_api(cls): 35 | __language_api__[cls.__name__] = cls 36 | return cls 37 | 38 | class ManagedIface(object): 39 | def __init__(self, manager, parent=None): 40 | self.manager = manager 41 | self.stack_manager = [] if parent is None else parent.stack_manager 42 | self.stack_manager.append(manager) 43 | self.recovery = {} 44 | def managed(cls): 45 | class Managed(cls): 46 | iface = self 47 | @property 48 | def mgr(_): 49 | return self.stack_manager[-1] 50 | Managed.__name__ = cls.__name__ 51 | return Managed 52 | for name, cls in __language_api__.items(): 53 | setattr(self, name, managed(cls)) 54 | 55 | def pop_manager(self): 56 | self.stack_manager.pop() 57 | 58 | def install(self, scope): 59 | sid = id(scope) 60 | is_dict = isinstance(scope, dict) 61 | rec = self.recovery[sid] = {} 62 | for k in __language_api__: 63 | hasv = (k in scope) if is_dict else hasattr(scope, k) 64 | if hasv: 65 | rec[k] = scope[k] if is_dict else getattr(scope, k) 66 | cls = getattr(self, k) 67 | setv = scope.__setitem__ if is_dict else scope.__setattr__ 68 | setv(k, getattr(self, k)) 69 | 70 | def uninstall(self, scope): 71 | sid = id(scope) 72 | is_dict = isinstance(scope, dict) 73 | rec = self.recovery[sid] 74 | for k in __language_api__: 75 | if k in rec: 76 | setv = scope.__setitem__ if is_dict else scope.__setattr__ 77 | setv(k, rec[k]) 78 | else: 79 | delv = scope.__delitem__ if is_dict else scope.__delattr__ 80 | delv(k) 81 | del self.recovery[sid] 82 | 83 | @language_api 84 | class Some(object): 85 | def __init__(self, name=None, **opts): 86 | self.name = name 87 | self.opts = opts 88 | self.dtype = self.__class__.__name__[4:] or None 89 | self.publish() 90 | def publish(self): 91 | self.mgr.register_some(self) 92 | def _decl_dtype(self, dtype): 93 | if self.dtype is None: 94 | self.dtype = dtype 95 | else: 96 | assert self.dtype == dtype, "Some used with incompatible types" 97 | def __str__(self): 98 | return f"Some{self.dtype}(\"{self.name}\")" 99 | def copy(self): 100 | return self 101 | 102 | def __ne__(left, right): 103 | if not isinstance(right, Some): 104 | raise TypeError() 105 | left.mgr.register_predicate("some_different", left, right) 106 | 107 | def assignments(self, solutions="subset-minimal", **kwargs): 108 | from .views import SomeView 109 | return SomeView(self, self.mgr.bo, solutions=solutions, **kwargs) 110 | def complementary_assignments(self, solutions="subset-minimal", **kwargs): 111 | if self.dtype == "Freeze": 112 | from .views import SomeFreezeComplementaryView 113 | return SomeFreezeComplementaryView(self, self.mgr.bo, 114 | solutions=solutions, **kwargs) 115 | raise TypeError() 116 | 117 | @language_api 118 | class SomeFreeze(Some): 119 | default_opts = { 120 | "min_size": 0, 121 | "max_size": 1, 122 | "exclude": (), 123 | "domain": (), 124 | } 125 | 126 | class BoContext(object): 127 | def __enter__(self): 128 | mgr = self._make_context() 129 | return ManagedIface(mgr, self.iface) 130 | def __exit__(self, *args): 131 | self.iface.pop_manager() 132 | 133 | @language_api 134 | class mutant(BoContext): 135 | def __init__(self, mutations): 136 | if isinstance(mutations, Some): 137 | mutations._decl_dtype("Freeze") 138 | else: 139 | for node in mutations: 140 | self.mgr.assert_node_exists(node) 141 | self.mutations = mutations 142 | def _make_context(self): 143 | return self.mgr.mutant_context(self.mutations) 144 | 145 | @language_api 146 | class action(mutant): 147 | def _make_context(self): 148 | return self.mgr.mutant_context(self.mutations, weak=True) 149 | 150 | @language_api 151 | class scope_reachability(BoContext): 152 | def __init__(self, **options): 153 | """ 154 | monotone: 155 | - if True, component having same value in origin and target 156 | configurations cannot oscillate 157 | max_changes: 158 | - if int, limit the dimension of the hypercube used from computing the 159 | reachability property 160 | """ 161 | self.__options = options 162 | def _make_context(self): 163 | return self.mgr.scope_reachability_context(self.__options) 164 | 165 | def declare_operator(operator): 166 | def decorator(func): 167 | def wrapper(K): 168 | setattr(K, operator, func) 169 | return K 170 | return wrapper 171 | return decorator 172 | 173 | @declare_operator("__ge__") # >= 174 | def reach_operator(left, right): 175 | return left.iface.reach(left, right) 176 | 177 | @declare_operator("__rshift__") # >> 178 | def allreach_operator(left, right): 179 | return left.iface.allreach(left, right) 180 | 181 | @declare_operator("__truediv__") # / 182 | def nonreach_operator(left, right): 183 | return left.iface.nonreach(left, right) 184 | 185 | @declare_operator("__floordiv__") # // 186 | def final_nonreach_operator(left, right): 187 | return left.iface.final_nonreach(left, right) 188 | 189 | @declare_operator("__ne__") # != 190 | def different_operator(left, right): 191 | return left.iface.different(left, right) 192 | 193 | 194 | class BonesisTerm(object): 195 | def __init__(self): 196 | assert hasattr(self, "mgr"), \ 197 | "do not instantiate non-managed class!" 198 | def _set_iface(self, iface): 199 | self.iface = iface 200 | self.mgr = self.iface.manager 201 | 202 | def __ge__(a, b): 203 | raise TypeError(f"'{a.__class__.__name__}' objects do not support '>=' operator") 204 | def __rshift__(a, b): 205 | raise TypeError(f"'{a.__class__.__name__}' objects do not support '>>' operator") 206 | def __truediv__(a, b): 207 | raise TypeError(f"'{a.__class__.__name__}' objects do not support '/' operator") 208 | def __floordiv__(a, b): 209 | raise TypeError(f"'{a.__class__.__name__}' objects do not support '//' operator") 210 | def __ne__(a, b): 211 | raise TypeError(f"'{a.__class__.__name__}' objects do not support '!=' operator") 212 | def __eq__(a, b): 213 | raise TypeError(f"'{a.__class__.__name__}' objects do not support '==' operator") 214 | 215 | class BonesisVar(BonesisTerm): 216 | def __init__(self, name): 217 | super().__init__() 218 | self.name = name 219 | self.publish() 220 | def publish(self): 221 | pass 222 | def __hash__(self): 223 | return hash((self.__class__.__name__,self.name)) 224 | 225 | @allreach_operator 226 | @nonreach_operator 227 | class ObservationVar(BonesisVar): 228 | def __init__(self, arg): 229 | if isinstance(arg, dict): 230 | name = None 231 | self.data = arg.copy() 232 | else: 233 | name = arg 234 | super().__init__(name) 235 | def publish(self): 236 | self.mgr.register_observation(self) 237 | def __invert__(self): 238 | return self.iface.cfg(self.name, obs=self) 239 | def __pos__(self): 240 | return self.iface.cfg(None, obs=self) 241 | def __str__(self): 242 | return f"Observation({repr(self.name)})" 243 | __language_api__["obs"] = ObservationVar 244 | 245 | class HypercubeVar(BonesisVar): 246 | def __init__(self, obs=None, 247 | min_dimension=0, 248 | max_dimension=None, 249 | dimension=None): 250 | if dimension is not None: 251 | min_dimension = max_dimension = dimension 252 | self.min_dimension = min_dimension 253 | self.max_dimension = max_dimension 254 | if isinstance(obs, dict): 255 | obs = self.iface.obs(obs) 256 | self.obs = obs 257 | super().__init__(None) 258 | def publish(self): 259 | self.mgr.register_hypercube(self) 260 | def __str__(self): 261 | return f"Hypercube({id(self)})" 262 | def assignments(self, **kwargs): 263 | from .views import HypercubeView 264 | return HypercubeView(self, self.mgr.bo, **kwargs) 265 | __language_api__["hypercube"] = HypercubeVar 266 | 267 | @different_operator 268 | @reach_operator 269 | @allreach_operator 270 | @nonreach_operator 271 | @final_nonreach_operator 272 | class ConfigurationVar(BonesisVar): 273 | def __init__(self, name=None, obs=None): 274 | self.obs = obs 275 | super().__init__(name) 276 | def publish(self): 277 | self.mgr.register_configuration(self) 278 | def __str__(self): 279 | return f"Configuration({repr(self.name or id(self))})" 280 | def __getitem__(self, node): 281 | return ConfigurationVarState(self, node) 282 | def __setitem__(self, node, right): 283 | self.mgr.assert_node_exists(node) 284 | if isinstance(right, bool): 285 | right = int(right) 286 | if isinstance(right, int): 287 | if not right in [0,1]: 288 | raise TypeError("cannot assign integers other than 0/1") 289 | self.mgr.register_predicate("cfg_assign", self, node, right) 290 | elif isinstance(right, ConfigurationVarState): 291 | self.mgr.register_predicate("cfg_node_eq", self, right.parent, node) 292 | else: 293 | raise TypeError(f"Invalid type for assignment {type(right)}") 294 | def assignments(self, **kwargs): 295 | from .views import ConfigurationView 296 | return ConfigurationView(self, self.mgr.bo, **kwargs) 297 | __language_api__["cfg"] = ConfigurationVar 298 | 299 | class ConfigurationVarState(object): 300 | def __init__(self, parent, node): 301 | self.parent = parent 302 | self.node = node 303 | def __eq__(self, b): 304 | self.parent[self.node] = b 305 | def __ne__(self, right): 306 | if not isinstance(right, ConfigurationVarState): 307 | raise TypeError(f"Invalid type for equality {type(right)}") 308 | self.parent.mgr.register_predicate("cfg_node_ne", 309 | self.parent, 310 | right.parent, 311 | self.node) 312 | 313 | 314 | @language_api 315 | class BonesisPredicate(BonesisTerm): 316 | def __init__(self, *args): 317 | self.args = args 318 | if not hasattr(self, "predicate_name"): 319 | self.predicate_name = self.__class__.__name__ 320 | def auto_iface(obj): 321 | if isinstance(obj, BonesisTerm): 322 | if not hasattr(self, "iface"): 323 | self._set_iface(obj.iface) 324 | elif isinstance(obj, (list, set, tuple)): 325 | [auto_iface(e) for e in obj] 326 | for obj in self.args: 327 | auto_iface(obj) 328 | assert hasattr(self, "mgr"), "Could not find manager" 329 | super().__init__() 330 | self.publish() 331 | @classmethod 332 | def type_error(celf): 333 | raise TypeError(f"Invalid arguments for {celf.__name__}") 334 | def publish(self): 335 | self.mgr.register_predicate(self.predicate_name, *self.args) 336 | def left(self): 337 | return self.args[0] 338 | def right(self): 339 | return self.args[-1] 340 | def __repr__(self): 341 | return f"{self.__class__.__name__}{tuple(map(repr,self.args))}" 342 | 343 | @language_api 344 | class constant(BonesisPredicate): 345 | def __init__(self, node, value): 346 | super().__init__(node, value) 347 | assert node in self.mgr.bo.domain 348 | 349 | @language_api 350 | @different_operator 351 | class fixed(BonesisPredicate): 352 | def __init__(self, arg): 353 | if isinstance(arg, ConfigurationVar): 354 | self.predicate_name = "fixpoint" 355 | elif isinstance(arg, ObservationVar): 356 | self.predicate_name = "trapspace" 357 | arg = +arg 358 | elif isinstance(arg, HypercubeVar): 359 | self.predicate_name = "attractor" 360 | else: 361 | self.type_error() 362 | super().__init__(arg) 363 | 364 | @language_api 365 | @different_operator 366 | class in_attractor(BonesisPredicate): 367 | def __init__(self, arg): 368 | if not isinstance(arg, ConfigurationVar): 369 | self.type_error() 370 | super().__init__(arg) 371 | 372 | @language_api 373 | class all_fixpoints(BonesisPredicate): 374 | _unit_types = (ObservationVar,ConfigurationVar) 375 | def __init__(self, arg): 376 | if isinstance(arg, (set, list, tuple)): 377 | for e in arg: 378 | if not isinstance(e, self._unit_types): 379 | self.type_error() 380 | elif isinstance(arg, self._unit_types): 381 | arg = (arg,) 382 | else: 383 | self.type_error() 384 | super().__init__(arg) 385 | 386 | 387 | @language_api 388 | class all_attractors_overlap(all_fixpoints): 389 | pass 390 | 391 | 392 | class _ConfigurableBinaryPredicate(BonesisPredicate): 393 | def __init__(self, left, right, options=None): 394 | left = self.left_arg(left) 395 | self.options = self.default_options if options is None else options 396 | self.closed = False 397 | if isinstance(right, str): 398 | self.options = (right,) 399 | elif isinstance(right, tuple) and set({type(t) for t in right}) == {str}: 400 | self.options = right 401 | else: 402 | self.closed = True 403 | right = self.right_arg(right) 404 | for opt in self.options: 405 | if opt not in self.supported_options: 406 | raise TypeError(f"unsupported option '{opt}'") 407 | super().__init__(left, right) 408 | 409 | def publish(self): 410 | if not self.closed: 411 | return 412 | self.mgr.register_predicate(self.predicate_name, self.options, *self.args) 413 | 414 | def __xor__(self, right): 415 | return self.__class__(self.left(), right, options=self.options) 416 | 417 | 418 | @language_api 419 | class allreach(_ConfigurableBinaryPredicate): 420 | _right_types = (ObservationVar,ConfigurationVar) 421 | supported_options = {"fixpoints", "attractors_overlap"} 422 | default_options = ("attractors_overlap",) 423 | """ 424 | left: cfg, reach(), obs 425 | right: obs, set([obs]) 426 | """ 427 | @classmethod 428 | def left_arg(celf, arg): 429 | if isinstance(arg, ConfigurationVar): 430 | return arg 431 | if isinstance(arg, reach): 432 | return celf.left_arg(arg.right()) 433 | celf.type_error() 434 | @classmethod 435 | def right_arg(celf, arg): 436 | if isinstance(arg, celf._right_types): 437 | return {arg} 438 | elif isinstance(arg, set): 439 | for elt in arg: 440 | if not isinstance(elt, celf._right_types): 441 | celf.type_error() 442 | return arg 443 | celf.type_error() 444 | 445 | 446 | @reach_operator 447 | @allreach_operator 448 | @nonreach_operator 449 | @final_nonreach_operator 450 | @language_api 451 | class reach(BonesisPredicate): 452 | """ 453 | left: cfg, reach() 454 | right: cfg, obs, reach(), fixed(), in_attractor() 455 | """ 456 | def __init__(self, left, right): 457 | left = self.left_arg(left) 458 | right = self.right_arg(right) 459 | super().__init__(left, right) 460 | @classmethod 461 | def left_arg(celf, arg): 462 | if isinstance(arg, ConfigurationVar): 463 | return arg 464 | if isinstance(arg, reach): 465 | return celf.left_arg(arg.right()) 466 | celf.type_error() 467 | @classmethod 468 | def right_arg(celf, arg): 469 | if isinstance(arg, (ConfigurationVar, ObservationVar)): 470 | return arg 471 | if isinstance(arg, (fixed, in_attractor, reach)): 472 | return celf.right_arg(arg.left()) 473 | celf.type_error() 474 | 475 | 476 | @language_api 477 | class nonreach(reach): 478 | def __init__(self, left, right): 479 | if isinstance(right, fixed): 480 | self.predicate_name = "final_nonreach" 481 | super().__init__(left, right) 482 | @classmethod 483 | def left_arg(celf, arg): 484 | if isinstance(arg, ObservationVar): 485 | return arg 486 | return super().left_arg(arg) 487 | 488 | @language_api 489 | class final_nonreach(nonreach): 490 | def __init__(self, left, right): 491 | if not isinstance(right, (fixed, in_attractor)): 492 | self.type_error() 493 | super().__init__(left, right) 494 | 495 | @language_api 496 | class different(BonesisPredicate): 497 | """ 498 | left: cfg, fixed(), in_attractor() 499 | right: cfg, fixed(), in_attractor(), obs 500 | """ 501 | def __init__(self, left, right): 502 | if isinstance(left, fixed): 503 | if left.predicate_name != "fixpoint": 504 | self.type_error() 505 | left = left.left() 506 | elif isinstance(left, in_attractor): 507 | left = left.left() 508 | if isinstance(right, fixed): 509 | if right.predicate_name != "fixpoint": 510 | self.type_error() 511 | right = right.right() 512 | elif isinstance(right, in_attractor): 513 | right = right.right() 514 | if not isinstance(left, ConfigurationVar) or \ 515 | not isinstance(right, (ConfigurationVar,ObservationVar)): 516 | self.type_error() 517 | super().__init__(left, right) 518 | 519 | @language_api 520 | class custom(BonesisPredicate): 521 | def __init__(self, arg): 522 | if not isinstance(arg, str): 523 | self.type_error() 524 | super().__init__(arg) 525 | 526 | 527 | class BonesisOptimization(object): 528 | def __init__(self): 529 | super().__init__() 530 | self.publish() 531 | def publish(self): 532 | name = self.__class__.__name__ 533 | idx = name.index("_") 534 | opt = name[:idx] 535 | name = name[idx+1:] 536 | self.mgr.append_optimization(opt, name) 537 | 538 | @language_api 539 | class maximize_nodes(BonesisOptimization): 540 | pass 541 | @language_api 542 | class maximize_constants(BonesisOptimization): 543 | pass 544 | @language_api 545 | class maximize_strong_constants(BonesisOptimization): 546 | pass 547 | -------------------------------------------------------------------------------- /bonesis/views.py: -------------------------------------------------------------------------------- 1 | # Copyright or © or Copr. Loïc Paulevé (2023) 2 | # 3 | # loic.pauleve@cnrs.fr 4 | # 5 | # This software is governed by the CeCILL license under French law and 6 | # abiding by the rules of distribution of free software. You can use, 7 | # modify and/ or redistribute the software under the terms of the CeCILL 8 | # license as circulated by CEA, CNRS and INRIA at the following URL 9 | # "http://www.cecill.info". 10 | # 11 | # As a counterpart to the access to the source code and rights to copy, 12 | # modify and redistribute granted by the license, users are provided only 13 | # with a limited warranty and the software's author, the holder of the 14 | # economic rights, and the successive licensors have only limited 15 | # liability. 16 | # 17 | # In this respect, the user's attention is drawn to the risks associated 18 | # with loading, using, modifying and/or developing or reproducing the 19 | # software by the user in light of its specific status of free software, 20 | # that may mean that it is complicated to manipulate, and that also 21 | # therefore means that it is reserved for developers and experienced 22 | # professionals having in-depth computer knowledge. Users are therefore 23 | # encouraged to load and test the software's suitability as regards their 24 | # requirements in conditions enabling the security of their systems and/or 25 | # data to be ensured and, more generally, to use and operate it in the 26 | # same conditions as regards security. 27 | # 28 | # The fact that you are presently reading this means that you have had 29 | # knowledge of the CeCILL license and that you accept its terms. 30 | # 31 | 32 | from functools import partial 33 | import itertools 34 | import multiprocessing 35 | import os 36 | from threading import Timer, Lock 37 | import time 38 | 39 | import clingo 40 | 41 | import networkx as nx 42 | import pandas as pd 43 | 44 | from .debug import dbg 45 | from .language import SomeFreeze 46 | from .snippets import bn_nocyclic_attractors 47 | from .utils import OverlayedDict, frozendict 48 | from bonesis0.asp_encoding import (minibn_of_facts, 49 | configurations_of_facts, 50 | parse_nb_threads, 51 | portfolio_path, 52 | py_of_symbol, symbol_of_py) 53 | from bonesis0.clingo_solving import setup_clingo_solve_handler 54 | from bonesis0.gil_utils import setup_gil_iterator 55 | from bonesis0 import diversity 56 | 57 | 58 | class BonesisView(object): 59 | single_shot = True 60 | def __init__(self, bo, limit=0, mode="auto", extra=None, progress=False, 61 | intermediate_model_cb=None, 62 | **settings): 63 | self.bo = bo 64 | self.aspmodel = bo.aspmodel 65 | self.limit = limit 66 | if mode == "auto": 67 | mode = "optN" if self.bo.has_optimizations() else "solve" 68 | self.mode = mode 69 | if mode.startswith("opt"): 70 | self.project = False 71 | self.progress = progress if mode.startswith("opt") else False 72 | self.settings = OverlayedDict(bo.settings) 73 | for k,v in settings.items(): 74 | self.settings[k] = v 75 | self.filters = [] 76 | self.callback_intermediate_model = intermediate_model_cb 77 | 78 | def parse_extra(extra): 79 | if isinstance(extra, str): 80 | if extra == "configurations": 81 | return configurations_of_facts 82 | elif extra == "boolean-network": 83 | return minibn_of_facts 84 | elif extra == "somes": 85 | return partial(AllSomeView.allsomes_from_atoms, 86 | self.bo.manager) 87 | raise ValueError(f"Unknown extra '{extra}'") 88 | return extra 89 | if isinstance(extra, (tuple, list)): 90 | extra = tuple(map(parse_extra, extra)) 91 | elif extra is not None: 92 | extra = (parse_extra(extra),) 93 | self.extra_model = extra 94 | 95 | def add_filter(self, func): 96 | self.filters.append(func) 97 | 98 | def configure(self, ground=True, **opts): 99 | args = [0] 100 | if self.single_shot and hasattr(clingo, "version") and clingo.version() >= (5,5,0): 101 | args.append("--single-shot") 102 | if self.project: 103 | args.append("--project") 104 | if self.mode == "optN": 105 | opt_strategy = self.settings.get("clingo_opt_strategy", "usc") 106 | args += ["--opt-mode=optN", f"--opt-strategy={opt_strategy}"] 107 | elif self.mode == "solve" and self.bo.has_optimizations(): 108 | args += ["--opt-mode=ignore"] 109 | 110 | settings = OverlayedDict(self.settings) 111 | if self.settings["solutions"] in ["subset-minimal", "subset-maximal"]: 112 | if parse_nb_threads(settings.get("parallel")) > 1: 113 | args += ["--configuration", portfolio_path('subset_portfolio')] 114 | args += ["--heuristic", "Domain", 115 | "--enum-mode", "domRec", 116 | "--dom-mod", "5,16" if self.settings["solutions"] == "subset-minimal" else "3,16"] 117 | 118 | if not self.settings["quiet"] and ground: 119 | print("Grounding...", end="", flush=True) 120 | start = time.process_time() 121 | self.control = self.bo.solver(*args, settings=settings, 122 | ground=False, **opts) 123 | self.interrupted = False 124 | self.configure_show() 125 | if ground: 126 | self.control.ground([("base",())]) 127 | if ground and not self.settings["quiet"]: 128 | end = time.process_time() 129 | print(f"done in {end-start:.1f}s") 130 | 131 | def configure_show(self): 132 | for tpl in self.show_templates: 133 | for x in self.aspmodel.show[tpl]: 134 | self.control.add("base", [], f"#show {x}.") 135 | 136 | def interrupt(self): 137 | dbg(f"{self} interrupted") 138 | self.interrupted = True 139 | self.control.interrupt() 140 | 141 | 142 | def __iter__(self): 143 | self.configure() 144 | self._solve_handler = setup_clingo_solve_handler(self.settings, 145 | self.control) 146 | self._iterator = iter(self._solve_handler) 147 | self._iterator = setup_gil_iterator(self.settings, self._iterator, 148 | self._solve_handler, self.control) 149 | self._counter = 0 150 | if self.progress: 151 | self._progressbar = self.progress(desc="Model optimization", 152 | total=float("inf")) 153 | return self 154 | 155 | def _progress_tick(self): 156 | if not self.progress: 157 | return 158 | if not self.mode.startswith("opt"): 159 | return 160 | self._progressbar.set_postfix({"score": self.cur_model.cost}, 161 | refresh=False) 162 | self._progressbar.update() 163 | self._progressbar.refresh() 164 | 165 | def _intermediate_model_found(self, model): 166 | if self.callback_intermediate_model: 167 | pmodel = self.parse_model(model) 168 | self.callback_intermediate_model(pmodel) 169 | 170 | def __next__(self): 171 | if self.limit and self._counter >= self.limit: 172 | if hasattr(self, "_solve_handler"): 173 | self._solve_handler.cancel() 174 | raise StopIteration 175 | 176 | self.cur_model = next(self._iterator) 177 | self._progress_tick() 178 | 179 | if self.mode == "opt": 180 | try: 181 | while True: 182 | self._intermediate_model_found(self.cur_model) 183 | self.cur_model = next(self._iterator) 184 | self._progress_tick() 185 | except StopIteration: 186 | if self.progress: 187 | self._progressbar.close() 188 | elif self.mode == "optN": 189 | while not self.cur_model.optimality_proven: 190 | self._intermediate_model_found(self.cur_model) 191 | self.cur_model = next(self._iterator) 192 | self._progress_tick() 193 | 194 | pmodel = self.parse_model(self.cur_model) 195 | for func in self.filters: 196 | if not func(pmodel): 197 | print(f"Skipping solution not verifying {func.__name__}") 198 | return next(self) 199 | self._counter += 1 200 | return pmodel 201 | 202 | def parse_model(self, m): 203 | model = self.format_model(m) 204 | if self.extra_model: 205 | atoms = m.symbols(atoms=True) 206 | extra = tuple((extra(atoms) for extra in self.extra_model)) 207 | model = (model,) + extra 208 | return model 209 | 210 | def count(self): 211 | k = self.parse_model 212 | if not self.filters: 213 | self.parse_model = lambda x: 1 214 | c = len(list(self)) 215 | self.parse_model = k 216 | return c 217 | 218 | def standalone(self, *args, **kwargs): 219 | self.configure(ground=False) 220 | return self.control.standalone(*args, **kwargs) 221 | 222 | 223 | class NodesView(BonesisView): 224 | project = True 225 | show_templates = ["node"] 226 | def format_model(self, model): 227 | atoms = model.symbols(shown=True) 228 | return {py_of_symbol(a.arguments[0]) for a in atoms\ 229 | if a.name == "node"} 230 | 231 | class NonConstantNodesView(BonesisView): 232 | project = True 233 | constants = "constant" 234 | show_templates = ["node", "strong_constant"] 235 | def format_model(self, model): 236 | atoms = model.symbols(shown=True) 237 | nodes = {py_of_symbol(a.arguments[0]) for a in atoms\ 238 | if a.name == "node"} 239 | constants = {py_of_symbol(a.arguments[0]) for a in atoms\ 240 | if a.name == self.constants} 241 | return nodes.difference(constants) 242 | 243 | class NonStrongConstantNodesView(NonConstantNodesView): 244 | constants = "strong_constant" 245 | show_templates = ["node", "strong_constant"] 246 | 247 | 248 | class InfluenceGraphView(BonesisView): 249 | project = True 250 | def configure_show(self): 251 | self.control.add("base", [], \ 252 | "#show."\ 253 | "#show node/1."\ 254 | "#show edge(A,B,S): clause(B,_,A,S).") 255 | 256 | def format_model(self, model): 257 | atoms = model.symbols(shown=True) 258 | return self.aspmodel.influence_graph_from_model(atoms) 259 | 260 | 261 | class BooleanNetworksView(BonesisView): 262 | project = True 263 | show_templates = ["boolean_network"] 264 | def __init__(self, *args, no_cyclic_attractors=False, **kwargs): 265 | super().__init__(*args, **kwargs) 266 | if no_cyclic_attractors: 267 | self.add_filter(bn_nocyclic_attractors) 268 | def format_model(self, model): 269 | atoms = model.symbols(shown=True) 270 | return minibn_of_facts(atoms) 271 | 272 | 273 | class ProjectedBooleanNetworksContext(object): 274 | def __init__(self, parent_view, nodes): 275 | self.parent = parent_view 276 | self.nodes = nodes 277 | self.externals = [clingo.Function("myshow", [clingo.String(n)])\ 278 | for n in self.nodes] 279 | 280 | def __enter__(self): 281 | self.parent.acquire() 282 | for e in self.externals: 283 | self.parent.control.assign_external(e, True) 284 | return self.parent 285 | 286 | def __exit__(self, *args): 287 | for e in self.externals: 288 | self.parent.control.assign_external(e, False) 289 | self.parent.release() 290 | 291 | 292 | class ProjectedBooleanNetworksViews(BooleanNetworksView): 293 | single_shot = False 294 | def __init__(self, *args, skip_empty=False, ground=True, **kwargs): 295 | super().__init__(*args, **kwargs) 296 | self.skip_empty = skip_empty 297 | super().configure(ground=ground) 298 | self.lock = Lock() 299 | 300 | def acquire(self): 301 | return self.lock.acquire(False) 302 | def release(self): 303 | return self.lock.release() 304 | 305 | def configure(self, **kwargs): 306 | return 307 | 308 | def configure_show(self): 309 | self.control.add("base", [], \ 310 | "#external myshow(N): node(N)."\ 311 | "#show."\ 312 | "#show clause(A,B,C,D): myshow(A), clause(A,B,C,D)."\ 313 | "#show constant(A,B): constant(A,B), myshow(A).") 314 | if self.skip_empty: 315 | self.control.add("base", [], "node(N) :- myshow(N).") 316 | 317 | def view(self, nodes): 318 | for n in nodes: 319 | if n not in self.bo.domain: 320 | raise ValueError(f"Undefined node '{n}'") 321 | return ProjectedBooleanNetworksContext(self, nodes) 322 | 323 | 324 | class LocalFunctionsViews(ProjectedBooleanNetworksViews): 325 | def view(self, node): 326 | return super().view((node,)) 327 | 328 | def format_model(self, model): 329 | bn = super().format_model(model) 330 | if not bn: 331 | return None 332 | return bn.popitem()[1] 333 | 334 | do = { 335 | "list": list, 336 | "count": lambda v: v.count(), 337 | } 338 | def as_dict(self, method="list", keys=None): 339 | if method not in self.do: 340 | raise ValueError("unknown method") 341 | func = self.do[method] 342 | d = {} 343 | nodes = self.bo.domain if keys is None else keys 344 | for n in nodes: 345 | with self.view(n) as fs: 346 | d[n] = func(fs) 347 | return d 348 | 349 | def as_dataframe(self, *args, **kwargs): 350 | d = self.as_dict(*args, **kwargs) 351 | return pd.DataFrame.from_dict(d, orient="index").fillna("").T 352 | 353 | 354 | class DiverseBooleanNetworksView(BooleanNetworksView): 355 | single_shot = False 356 | project = False 357 | def __init__(self, bo, driver="fraction", 358 | driver_kwargs=dict(pc_drive=50, pc_forget=50), 359 | skip_supersets=False, 360 | **kwargs): 361 | super().__init__(bo, **kwargs) 362 | self.driver_cls = driver if type(driver) is not str else \ 363 | getattr(diversity, f"diversity_driver_{driver}") 364 | self.driver_kwargs = driver_kwargs 365 | self.skip_supersets = skip_supersets 366 | 367 | def configure(self, **opts): 368 | super().configure(**opts) 369 | self.driver = self.driver_cls(**self.driver_kwargs) 370 | self.diverse = diversity.solve_diverse(self.control.control, self.driver, 371 | limit=self.limit, on_model=super().parse_model, 372 | skip_supersets=self.skip_supersets, 373 | settings=self.settings) 374 | 375 | def parse_model(self, model): 376 | return model 377 | 378 | def __iter__(self): 379 | self.configure() 380 | self._iterator = iter(self.diverse) 381 | self._counter = 0 382 | return self 383 | 384 | class ConfigurationView(BonesisView): 385 | project = True 386 | _pred_name = "cfg" 387 | def __init__(self, cfg, *args, scope=None, **kwargs): 388 | super().__init__(*args, **kwargs) 389 | self.cfg = cfg 390 | self.scope = scope 391 | def configure_show(self): 392 | name = symbol_of_py(self.cfg.name) 393 | self.control.add("base", [], "#show.") 394 | if self.scope is not None: 395 | for n in self.scope: 396 | n = symbol_of_py(n) 397 | self.control.add("base", [], f"show_scope({self._pred_name}({name},{n})).") 398 | self.control.add("base", [], f"#show {self._pred_name}(X,N,V) : " 399 | f"{self._pred_name}(X,N,V), X={name}," 400 | f"show_scope({self._pred_name}(X,N)).") 401 | else: 402 | self.control.add("base", [], f"#show {self._pred_name}(X,N,V) :" 403 | f"{self._pred_name}(X,N,V), X={name}.") 404 | def format_model(self, model): 405 | atoms = model.symbols(shown=True) 406 | x = self.cfg.name 407 | return configurations_of_facts(atoms, keys=[x])[x] 408 | 409 | class HypercubeView(ConfigurationView): 410 | _pred_name = "hypercube" 411 | def format_model(self, model): 412 | pairs = [] 413 | for a in model.symbols(shown=True): 414 | _, n, v = py_of_symbol(a) 415 | if v == 2: 416 | v = '*' 417 | elif v == -1: 418 | v = 0 419 | pairs.append((n,v)) 420 | return dict(sorted(pairs)) 421 | 422 | class AllSomeView(BonesisView): 423 | project = True 424 | show_templates = ["some"] 425 | 426 | @staticmethod 427 | def allsomes_from_atoms(manager, atoms): 428 | def init_some(dtype): 429 | if dtype == "Freeze": 430 | return {} 431 | raise NotImplementedError 432 | somes = {name: init_some(some.dtype) 433 | for name, some in manager.some.items()} 434 | 435 | for a in atoms: 436 | if a.name == "some_freeze": 437 | name, n, v = py_of_symbol(a) 438 | somes[name][n] = max(v,0) 439 | return somes 440 | 441 | def format_model(self, model): 442 | atoms = model.symbols(shown=True) 443 | return self.allsomes_from_atoms(self.bo.manager, atoms) 444 | 445 | class SomeView(AllSomeView): 446 | def __init__(self, some, *args, **kwargs): 447 | super().__init__(*args, **kwargs) 448 | self.some = some 449 | def configure_show(self): 450 | if self.some.dtype == "Freeze": 451 | name = symbol_of_py(self.some.name) 452 | self.control.add("base", [], 453 | "#show." 454 | f"#show some_freeze(M,N,V) : some_freeze(M,N,V), M={name}.") 455 | else: 456 | raise NotImplementedError 457 | def format_model(self, model): 458 | somes = super().format_model(model) 459 | return somes[self.some.name] 460 | 461 | def SomeFreezeComplementaryView(some, *args, **kwargs): 462 | subset_min = kwargs["solutions"] == "subset-minimal" 463 | 464 | kwargs["solutions"] = "all" 465 | coview = SomeView(some, *args, **kwargs) 466 | opts = SomeFreeze.default_opts | some.opts 467 | 468 | nodes = list(some.mgr.bo.domain) 469 | if opts["exclude"]: 470 | nodes = [n for n in nodes if n not in opts["exclude"]] 471 | elements = [(n,0) for n in nodes] + [(n,1) for n in nodes] 472 | 473 | def freeze_add(fs, e): 474 | coe = (e[0], 1-e[1]) 475 | if coe in fs: 476 | return fs 477 | return fs.union((e,)) 478 | 479 | def enlarge_candidates(candidates, elements): 480 | return map(lambda y: freeze_add(*y), 481 | itertools.product(candidates, elements)) 482 | 483 | candidates = [frozendict({})] 484 | for _ in range(opts["min_size"]): 485 | candidates = enlarge_candidates(candidates, elements) 486 | 487 | min_size = opts["min_size"] 488 | max_size = opts["max_size"] 489 | good = set() 490 | for size in range(min_size, max_size+1): 491 | some.opts["min_size"] = size 492 | some.opts["max_size"] = size 493 | coassignments = set(map(frozendict, coview)) 494 | 495 | bad = set() 496 | for candidate in candidates: 497 | if len(candidate) != size: 498 | continue 499 | if candidate not in coassignments: 500 | ignore = False 501 | for g in good: 502 | if g.issubset(candidate): 503 | ignore = True 504 | break 505 | if not ignore: 506 | yield dict(candidate) 507 | if subset_min and size > 1: 508 | good.add(candidate) 509 | else: 510 | bad.add(candidate) 511 | if size == 0 and not bad: 512 | break 513 | if size != opts["max_size"]: 514 | if subset_min and size == 1: 515 | elements = [next(iter(c)) for c in bad] 516 | if not elements: 517 | break 518 | candidates = enlarge_candidates(bad, elements) 519 | # restore 520 | opts["min_size"] = min_size 521 | opts["max_size"] = max_size 522 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | CeCILL FREE SOFTWARE LICENSE AGREEMENT 3 | 4 | Version 2.1 dated 2013-06-21 5 | 6 | 7 | Notice 8 | 9 | This Agreement is a Free Software license agreement that is the result 10 | of discussions between its authors in order to ensure compliance with 11 | the two main principles guiding its drafting: 12 | 13 | * firstly, compliance with the principles governing the distribution 14 | of Free Software: access to source code, broad rights granted to users, 15 | * secondly, the election of a governing law, French law, with which it 16 | is conformant, both as regards the law of torts and intellectual 17 | property law, and the protection that it offers to both authors and 18 | holders of the economic rights over software. 19 | 20 | The authors of the CeCILL (for Ce[a] C[nrs] I[nria] L[ogiciel] L[ibre]) 21 | license are: 22 | 23 | Commissariat à l'énergie atomique et aux énergies alternatives - CEA, a 24 | public scientific, technical and industrial research establishment, 25 | having its principal place of business at 25 rue Leblanc, immeuble Le 26 | Ponant D, 75015 Paris, France. 27 | 28 | Centre National de la Recherche Scientifique - CNRS, a public scientific 29 | and technological establishment, having its principal place of business 30 | at 3 rue Michel-Ange, 75794 Paris cedex 16, France. 31 | 32 | Institut National de Recherche en Informatique et en Automatique - 33 | Inria, a public scientific and technological establishment, having its 34 | principal place of business at Domaine de Voluceau, Rocquencourt, BP 35 | 105, 78153 Le Chesnay cedex, France. 36 | 37 | 38 | Preamble 39 | 40 | The purpose of this Free Software license agreement is to grant users 41 | the right to modify and redistribute the software governed by this 42 | license within the framework of an open source distribution model. 43 | 44 | The exercising of this right is conditional upon certain obligations for 45 | users so as to preserve this status for all subsequent redistributions. 46 | 47 | In consideration of access to the source code and the rights to copy, 48 | modify and redistribute granted by the license, users are provided only 49 | with a limited warranty and the software's author, the holder of the 50 | economic rights, and the successive licensors only have limited liability. 51 | 52 | In this respect, the risks associated with loading, using, modifying 53 | and/or developing or reproducing the software by the user are brought to 54 | the user's attention, given its Free Software status, which may make it 55 | complicated to use, with the result that its use is reserved for 56 | developers and experienced professionals having in-depth computer 57 | knowledge. Users are therefore encouraged to load and test the 58 | suitability of the software as regards their requirements in conditions 59 | enabling the security of their systems and/or data to be ensured and, 60 | more generally, to use and operate it in the same conditions of 61 | security. This Agreement may be freely reproduced and published, 62 | provided it is not altered, and that no provisions are either added or 63 | removed herefrom. 64 | 65 | This Agreement may apply to any or all software for which the holder of 66 | the economic rights decides to submit the use thereof to its provisions. 67 | 68 | Frequently asked questions can be found on the official website of the 69 | CeCILL licenses family (http://www.cecill.info/index.en.html) for any 70 | necessary clarification. 71 | 72 | 73 | Article 1 - DEFINITIONS 74 | 75 | For the purpose of this Agreement, when the following expressions 76 | commence with a capital letter, they shall have the following meaning: 77 | 78 | Agreement: means this license agreement, and its possible subsequent 79 | versions and annexes. 80 | 81 | Software: means the software in its Object Code and/or Source Code form 82 | and, where applicable, its documentation, "as is" when the Licensee 83 | accepts the Agreement. 84 | 85 | Initial Software: means the Software in its Source Code and possibly its 86 | Object Code form and, where applicable, its documentation, "as is" when 87 | it is first distributed under the terms and conditions of the Agreement. 88 | 89 | Modified Software: means the Software modified by at least one 90 | Contribution. 91 | 92 | Source Code: means all the Software's instructions and program lines to 93 | which access is required so as to modify the Software. 94 | 95 | Object Code: means the binary files originating from the compilation of 96 | the Source Code. 97 | 98 | Holder: means the holder(s) of the economic rights over the Initial 99 | Software. 100 | 101 | Licensee: means the Software user(s) having accepted the Agreement. 102 | 103 | Contributor: means a Licensee having made at least one Contribution. 104 | 105 | Licensor: means the Holder, or any other individual or legal entity, who 106 | distributes the Software under the Agreement. 107 | 108 | Contribution: means any or all modifications, corrections, translations, 109 | adaptations and/or new functions integrated into the Software by any or 110 | all Contributors, as well as any or all Internal Modules. 111 | 112 | Module: means a set of sources files including their documentation that 113 | enables supplementary functions or services in addition to those offered 114 | by the Software. 115 | 116 | External Module: means any or all Modules, not derived from the 117 | Software, so that this Module and the Software run in separate address 118 | spaces, with one calling the other when they are run. 119 | 120 | Internal Module: means any or all Module, connected to the Software so 121 | that they both execute in the same address space. 122 | 123 | GNU GPL: means the GNU General Public License version 2 or any 124 | subsequent version, as published by the Free Software Foundation Inc. 125 | 126 | GNU Affero GPL: means the GNU Affero General Public License version 3 or 127 | any subsequent version, as published by the Free Software Foundation Inc. 128 | 129 | EUPL: means the European Union Public License version 1.1 or any 130 | subsequent version, as published by the European Commission. 131 | 132 | Parties: mean both the Licensee and the Licensor. 133 | 134 | These expressions may be used both in singular and plural form. 135 | 136 | 137 | Article 2 - PURPOSE 138 | 139 | The purpose of the Agreement is the grant by the Licensor to the 140 | Licensee of a non-exclusive, transferable and worldwide license for the 141 | Software as set forth in Article 5 <#scope> hereinafter for the whole 142 | term of the protection granted by the rights over said Software. 143 | 144 | 145 | Article 3 - ACCEPTANCE 146 | 147 | 3.1 The Licensee shall be deemed as having accepted the terms and 148 | conditions of this Agreement upon the occurrence of the first of the 149 | following events: 150 | 151 | * (i) loading the Software by any or all means, notably, by 152 | downloading from a remote server, or by loading from a physical medium; 153 | * (ii) the first time the Licensee exercises any of the rights granted 154 | hereunder. 155 | 156 | 3.2 One copy of the Agreement, containing a notice relating to the 157 | characteristics of the Software, to the limited warranty, and to the 158 | fact that its use is restricted to experienced users has been provided 159 | to the Licensee prior to its acceptance as set forth in Article 3.1 160 | <#accepting> hereinabove, and the Licensee hereby acknowledges that it 161 | has read and understood it. 162 | 163 | 164 | Article 4 - EFFECTIVE DATE AND TERM 165 | 166 | 167 | 4.1 EFFECTIVE DATE 168 | 169 | The Agreement shall become effective on the date when it is accepted by 170 | the Licensee as set forth in Article 3.1 <#accepting>. 171 | 172 | 173 | 4.2 TERM 174 | 175 | The Agreement shall remain in force for the entire legal term of 176 | protection of the economic rights over the Software. 177 | 178 | 179 | Article 5 - SCOPE OF RIGHTS GRANTED 180 | 181 | The Licensor hereby grants to the Licensee, who accepts, the following 182 | rights over the Software for any or all use, and for the term of the 183 | Agreement, on the basis of the terms and conditions set forth hereinafter. 184 | 185 | Besides, if the Licensor owns or comes to own one or more patents 186 | protecting all or part of the functions of the Software or of its 187 | components, the Licensor undertakes not to enforce the rights granted by 188 | these patents against successive Licensees using, exploiting or 189 | modifying the Software. If these patents are transferred, the Licensor 190 | undertakes to have the transferees subscribe to the obligations set 191 | forth in this paragraph. 192 | 193 | 194 | 5.1 RIGHT OF USE 195 | 196 | The Licensee is authorized to use the Software, without any limitation 197 | as to its fields of application, with it being hereinafter specified 198 | that this comprises: 199 | 200 | 1. permanent or temporary reproduction of all or part of the Software 201 | by any or all means and in any or all form. 202 | 203 | 2. loading, displaying, running, or storing the Software on any or all 204 | medium. 205 | 206 | 3. entitlement to observe, study or test its operation so as to 207 | determine the ideas and principles behind any or all constituent 208 | elements of said Software. This shall apply when the Licensee 209 | carries out any or all loading, displaying, running, transmission or 210 | storage operation as regards the Software, that it is entitled to 211 | carry out hereunder. 212 | 213 | 214 | 5.2 ENTITLEMENT TO MAKE CONTRIBUTIONS 215 | 216 | The right to make Contributions includes the right to translate, adapt, 217 | arrange, or make any or all modifications to the Software, and the right 218 | to reproduce the resulting software. 219 | 220 | The Licensee is authorized to make any or all Contributions to the 221 | Software provided that it includes an explicit notice that it is the 222 | author of said Contribution and indicates the date of the creation thereof. 223 | 224 | 225 | 5.3 RIGHT OF DISTRIBUTION 226 | 227 | In particular, the right of distribution includes the right to publish, 228 | transmit and communicate the Software to the general public on any or 229 | all medium, and by any or all means, and the right to market, either in 230 | consideration of a fee, or free of charge, one or more copies of the 231 | Software by any means. 232 | 233 | The Licensee is further authorized to distribute copies of the modified 234 | or unmodified Software to third parties according to the terms and 235 | conditions set forth hereinafter. 236 | 237 | 238 | 5.3.1 DISTRIBUTION OF SOFTWARE WITHOUT MODIFICATION 239 | 240 | The Licensee is authorized to distribute true copies of the Software in 241 | Source Code or Object Code form, provided that said distribution 242 | complies with all the provisions of the Agreement and is accompanied by: 243 | 244 | 1. a copy of the Agreement, 245 | 246 | 2. a notice relating to the limitation of both the Licensor's warranty 247 | and liability as set forth in Articles 8 and 9, 248 | 249 | and that, in the event that only the Object Code of the Software is 250 | redistributed, the Licensee allows effective access to the full Source 251 | Code of the Software for a period of at least three years from the 252 | distribution of the Software, it being understood that the additional 253 | acquisition cost of the Source Code shall not exceed the cost of the 254 | data transfer. 255 | 256 | 257 | 5.3.2 DISTRIBUTION OF MODIFIED SOFTWARE 258 | 259 | When the Licensee makes a Contribution to the Software, the terms and 260 | conditions for the distribution of the resulting Modified Software 261 | become subject to all the provisions of this Agreement. 262 | 263 | The Licensee is authorized to distribute the Modified Software, in 264 | source code or object code form, provided that said distribution 265 | complies with all the provisions of the Agreement and is accompanied by: 266 | 267 | 1. a copy of the Agreement, 268 | 269 | 2. a notice relating to the limitation of both the Licensor's warranty 270 | and liability as set forth in Articles 8 and 9, 271 | 272 | and, in the event that only the object code of the Modified Software is 273 | redistributed, 274 | 275 | 3. a note stating the conditions of effective access to the full source 276 | code of the Modified Software for a period of at least three years 277 | from the distribution of the Modified Software, it being understood 278 | that the additional acquisition cost of the source code shall not 279 | exceed the cost of the data transfer. 280 | 281 | 282 | 5.3.3 DISTRIBUTION OF EXTERNAL MODULES 283 | 284 | When the Licensee has developed an External Module, the terms and 285 | conditions of this Agreement do not apply to said External Module, that 286 | may be distributed under a separate license agreement. 287 | 288 | 289 | 5.3.4 COMPATIBILITY WITH OTHER LICENSES 290 | 291 | The Licensee can include a code that is subject to the provisions of one 292 | of the versions of the GNU GPL, GNU Affero GPL and/or EUPL in the 293 | Modified or unmodified Software, and distribute that entire code under 294 | the terms of the same version of the GNU GPL, GNU Affero GPL and/or EUPL. 295 | 296 | The Licensee can include the Modified or unmodified Software in a code 297 | that is subject to the provisions of one of the versions of the GNU GPL, 298 | GNU Affero GPL and/or EUPL and distribute that entire code under the 299 | terms of the same version of the GNU GPL, GNU Affero GPL and/or EUPL. 300 | 301 | 302 | Article 6 - INTELLECTUAL PROPERTY 303 | 304 | 305 | 6.1 OVER THE INITIAL SOFTWARE 306 | 307 | The Holder owns the economic rights over the Initial Software. Any or 308 | all use of the Initial Software is subject to compliance with the terms 309 | and conditions under which the Holder has elected to distribute its work 310 | and no one shall be entitled to modify the terms and conditions for the 311 | distribution of said Initial Software. 312 | 313 | The Holder undertakes that the Initial Software will remain ruled at 314 | least by this Agreement, for the duration set forth in Article 4.2 <#term>. 315 | 316 | 317 | 6.2 OVER THE CONTRIBUTIONS 318 | 319 | The Licensee who develops a Contribution is the owner of the 320 | intellectual property rights over this Contribution as defined by 321 | applicable law. 322 | 323 | 324 | 6.3 OVER THE EXTERNAL MODULES 325 | 326 | The Licensee who develops an External Module is the owner of the 327 | intellectual property rights over this External Module as defined by 328 | applicable law and is free to choose the type of agreement that shall 329 | govern its distribution. 330 | 331 | 332 | 6.4 JOINT PROVISIONS 333 | 334 | The Licensee expressly undertakes: 335 | 336 | 1. not to remove, or modify, in any manner, the intellectual property 337 | notices attached to the Software; 338 | 339 | 2. to reproduce said notices, in an identical manner, in the copies of 340 | the Software modified or not. 341 | 342 | The Licensee undertakes not to directly or indirectly infringe the 343 | intellectual property rights on the Software of the Holder and/or 344 | Contributors, and to take, where applicable, vis-à-vis its staff, any 345 | and all measures required to ensure respect of said intellectual 346 | property rights of the Holder and/or Contributors. 347 | 348 | 349 | Article 7 - RELATED SERVICES 350 | 351 | 7.1 Under no circumstances shall the Agreement oblige the Licensor to 352 | provide technical assistance or maintenance services for the Software. 353 | 354 | However, the Licensor is entitled to offer this type of services. The 355 | terms and conditions of such technical assistance, and/or such 356 | maintenance, shall be set forth in a separate instrument. Only the 357 | Licensor offering said maintenance and/or technical assistance services 358 | shall incur liability therefor. 359 | 360 | 7.2 Similarly, any Licensor is entitled to offer to its licensees, under 361 | its sole responsibility, a warranty, that shall only be binding upon 362 | itself, for the redistribution of the Software and/or the Modified 363 | Software, under terms and conditions that it is free to decide. Said 364 | warranty, and the financial terms and conditions of its application, 365 | shall be subject of a separate instrument executed between the Licensor 366 | and the Licensee. 367 | 368 | 369 | Article 8 - LIABILITY 370 | 371 | 8.1 Subject to the provisions of Article 8.2, the Licensee shall be 372 | entitled to claim compensation for any direct loss it may have suffered 373 | from the Software as a result of a fault on the part of the relevant 374 | Licensor, subject to providing evidence thereof. 375 | 376 | 8.2 The Licensor's liability is limited to the commitments made under 377 | this Agreement and shall not be incurred as a result of in particular: 378 | (i) loss due the Licensee's total or partial failure to fulfill its 379 | obligations, (ii) direct or consequential loss that is suffered by the 380 | Licensee due to the use or performance of the Software, and (iii) more 381 | generally, any consequential loss. In particular the Parties expressly 382 | agree that any or all pecuniary or business loss (i.e. loss of data, 383 | loss of profits, operating loss, loss of customers or orders, 384 | opportunity cost, any disturbance to business activities) or any or all 385 | legal proceedings instituted against the Licensee by a third party, 386 | shall constitute consequential loss and shall not provide entitlement to 387 | any or all compensation from the Licensor. 388 | 389 | 390 | Article 9 - WARRANTY 391 | 392 | 9.1 The Licensee acknowledges that the scientific and technical 393 | state-of-the-art when the Software was distributed did not enable all 394 | possible uses to be tested and verified, nor for the presence of 395 | possible defects to be detected. In this respect, the Licensee's 396 | attention has been drawn to the risks associated with loading, using, 397 | modifying and/or developing and reproducing the Software which are 398 | reserved for experienced users. 399 | 400 | The Licensee shall be responsible for verifying, by any or all means, 401 | the suitability of the product for its requirements, its good working 402 | order, and for ensuring that it shall not cause damage to either persons 403 | or properties. 404 | 405 | 9.2 The Licensor hereby represents, in good faith, that it is entitled 406 | to grant all the rights over the Software (including in particular the 407 | rights set forth in Article 5 <#scope>). 408 | 409 | 9.3 The Licensee acknowledges that the Software is supplied "as is" by 410 | the Licensor without any other express or tacit warranty, other than 411 | that provided for in Article 9.2 <#good-faith> and, in particular, 412 | without any warranty as to its commercial value, its secured, safe, 413 | innovative or relevant nature. 414 | 415 | Specifically, the Licensor does not warrant that the Software is free 416 | from any error, that it will operate without interruption, that it will 417 | be compatible with the Licensee's own equipment and software 418 | configuration, nor that it will meet the Licensee's requirements. 419 | 420 | 9.4 The Licensor does not either expressly or tacitly warrant that the 421 | Software does not infringe any third party intellectual property right 422 | relating to a patent, software or any other property right. Therefore, 423 | the Licensor disclaims any and all liability towards the Licensee 424 | arising out of any or all proceedings for infringement that may be 425 | instituted in respect of the use, modification and redistribution of the 426 | Software. Nevertheless, should such proceedings be instituted against 427 | the Licensee, the Licensor shall provide it with technical and legal 428 | expertise for its defense. Such technical and legal expertise shall be 429 | decided on a case-by-case basis between the relevant Licensor and the 430 | Licensee pursuant to a memorandum of understanding. The Licensor 431 | disclaims any and all liability as regards the Licensee's use of the 432 | name of the Software. No warranty is given as regards the existence of 433 | prior rights over the name of the Software or as regards the existence 434 | of a trademark. 435 | 436 | 437 | Article 10 - TERMINATION 438 | 439 | 10.1 In the event of a breach by the Licensee of its obligations 440 | hereunder, the Licensor may automatically terminate this Agreement 441 | thirty (30) days after notice has been sent to the Licensee and has 442 | remained ineffective. 443 | 444 | 10.2 A Licensee whose Agreement is terminated shall no longer be 445 | authorized to use, modify or distribute the Software. However, any 446 | licenses that it may have granted prior to termination of the Agreement 447 | shall remain valid subject to their having been granted in compliance 448 | with the terms and conditions hereof. 449 | 450 | 451 | Article 11 - MISCELLANEOUS 452 | 453 | 454 | 11.1 EXCUSABLE EVENTS 455 | 456 | Neither Party shall be liable for any or all delay, or failure to 457 | perform the Agreement, that may be attributable to an event of force 458 | majeure, an act of God or an outside cause, such as defective 459 | functioning or interruptions of the electricity or telecommunications 460 | networks, network paralysis following a virus attack, intervention by 461 | government authorities, natural disasters, water damage, earthquakes, 462 | fire, explosions, strikes and labor unrest, war, etc. 463 | 464 | 11.2 Any failure by either Party, on one or more occasions, to invoke 465 | one or more of the provisions hereof, shall under no circumstances be 466 | interpreted as being a waiver by the interested Party of its right to 467 | invoke said provision(s) subsequently. 468 | 469 | 11.3 The Agreement cancels and replaces any or all previous agreements, 470 | whether written or oral, between the Parties and having the same 471 | purpose, and constitutes the entirety of the agreement between said 472 | Parties concerning said purpose. No supplement or modification to the 473 | terms and conditions hereof shall be effective as between the Parties 474 | unless it is made in writing and signed by their duly authorized 475 | representatives. 476 | 477 | 11.4 In the event that one or more of the provisions hereof were to 478 | conflict with a current or future applicable act or legislative text, 479 | said act or legislative text shall prevail, and the Parties shall make 480 | the necessary amendments so as to comply with said act or legislative 481 | text. All other provisions shall remain effective. Similarly, invalidity 482 | of a provision of the Agreement, for any reason whatsoever, shall not 483 | cause the Agreement as a whole to be invalid. 484 | 485 | 486 | 11.5 LANGUAGE 487 | 488 | The Agreement is drafted in both French and English and both versions 489 | are deemed authentic. 490 | 491 | 492 | Article 12 - NEW VERSIONS OF THE AGREEMENT 493 | 494 | 12.1 Any person is authorized to duplicate and distribute copies of this 495 | Agreement. 496 | 497 | 12.2 So as to ensure coherence, the wording of this Agreement is 498 | protected and may only be modified by the authors of the License, who 499 | reserve the right to periodically publish updates or new versions of the 500 | Agreement, each with a separate number. These subsequent versions may 501 | address new issues encountered by Free Software. 502 | 503 | 12.3 Any Software distributed under a given version of the Agreement may 504 | only be subsequently distributed under the same version of the Agreement 505 | or a subsequent version, subject to the provisions of Article 5.3.4 506 | <#compatibility>. 507 | 508 | 509 | Article 13 - GOVERNING LAW AND JURISDICTION 510 | 511 | 13.1 The Agreement is governed by French law. The Parties agree to 512 | endeavor to seek an amicable solution to any disagreements or disputes 513 | that may arise during the performance of the Agreement. 514 | 515 | 13.2 Failing an amicable solution within two (2) months as from their 516 | occurrence, and unless emergency proceedings are necessary, the 517 | disagreements or disputes shall be referred to the Paris Courts having 518 | jurisdiction, by the more diligent Party. 519 | 520 | -------------------------------------------------------------------------------- /Licence_CeCILL_V2.1-fr.txt: -------------------------------------------------------------------------------- 1 | 2 | CONTRAT DE LICENCE DE LOGICIEL LIBRE CeCILL 3 | 4 | Version 2.1 du 2013-06-21 5 | 6 | 7 | Avertissement 8 | 9 | Ce contrat est une licence de logiciel libre issue d'une concertation 10 | entre ses auteurs afin que le respect de deux grands principes préside à 11 | sa rédaction: 12 | 13 | * d'une part, le respect des principes de diffusion des logiciels 14 | libres: accès au code source, droits étendus conférés aux utilisateurs, 15 | * d'autre part, la désignation d'un droit applicable, le droit 16 | français, auquel elle est conforme, tant au regard du droit de la 17 | responsabilité civile que du droit de la propriété intellectuelle et 18 | de la protection qu'il offre aux auteurs et titulaires des droits 19 | patrimoniaux sur un logiciel. 20 | 21 | Les auteurs de la licence CeCILL (Ce[a] C[nrs] I[nria] L[ogiciel] L[ibre]) 22 | sont: 23 | 24 | Commissariat à l'énergie atomique et aux énergies alternatives - CEA, 25 | établissement public de recherche à caractère scientifique, technique et 26 | industriel, dont le siège est situé 25 rue Leblanc, immeuble Le Ponant 27 | D, 75015 Paris. 28 | 29 | Centre National de la Recherche Scientifique - CNRS, établissement 30 | public à caractère scientifique et technologique, dont le siège est 31 | situé 3 rue Michel-Ange, 75794 Paris cedex 16. 32 | 33 | Institut National de Recherche en Informatique et en Automatique - 34 | Inria, établissement public à caractère scientifique et technologique, 35 | dont le siège est situé Domaine de Voluceau, Rocquencourt, BP 105, 78153 36 | Le Chesnay cedex. 37 | 38 | 39 | Préambule 40 | 41 | Ce contrat est une licence de logiciel libre dont l'objectif est de 42 | conférer aux utilisateurs la liberté de modification et de 43 | redistribution du logiciel régi par cette licence dans le cadre d'un 44 | modèle de diffusion en logiciel libre. 45 | 46 | L'exercice de ces libertés est assorti de certains devoirs à la charge 47 | des utilisateurs afin de préserver ce statut au cours des 48 | redistributions ultérieures. 49 | 50 | L'accessibilité au code source et les droits de copie, de modification 51 | et de redistribution qui en découlent ont pour contrepartie de n'offrir 52 | aux utilisateurs qu'une garantie limitée et de ne faire peser sur 53 | l'auteur du logiciel, le titulaire des droits patrimoniaux et les 54 | concédants successifs qu'une responsabilité restreinte. 55 | 56 | A cet égard l'attention de l'utilisateur est attirée sur les risques 57 | associés au chargement, à l'utilisation, à la modification et/ou au 58 | développement et à la reproduction du logiciel par l'utilisateur étant 59 | donné sa spécificité de logiciel libre, qui peut le rendre complexe à 60 | manipuler et qui le réserve donc à des développeurs ou des 61 | professionnels avertis possédant des connaissances informatiques 62 | approfondies. Les utilisateurs sont donc invités à charger et tester 63 | l'adéquation du logiciel à leurs besoins dans des conditions permettant 64 | d'assurer la sécurité de leurs systèmes et/ou de leurs données et, plus 65 | généralement, à l'utiliser et l'exploiter dans les mêmes conditions de 66 | sécurité. Ce contrat peut être reproduit et diffusé librement, sous 67 | réserve de le conserver en l'état, sans ajout ni suppression de clauses. 68 | 69 | Ce contrat est susceptible de s'appliquer à tout logiciel dont le 70 | titulaire des droits patrimoniaux décide de soumettre l'exploitation aux 71 | dispositions qu'il contient. 72 | 73 | Une liste de questions fréquemment posées se trouve sur le site web 74 | officiel de la famille des licences CeCILL 75 | (http://www.cecill.info/index.fr.html) pour toute clarification qui 76 | serait nécessaire. 77 | 78 | 79 | Article 1 - DEFINITIONS 80 | 81 | Dans ce contrat, les termes suivants, lorsqu'ils seront écrits avec une 82 | lettre capitale, auront la signification suivante: 83 | 84 | Contrat: désigne le présent contrat de licence, ses éventuelles versions 85 | postérieures et annexes. 86 | 87 | Logiciel: désigne le logiciel sous sa forme de Code Objet et/ou de Code 88 | Source et le cas échéant sa documentation, dans leur état au moment de 89 | l'acceptation du Contrat par le Licencié. 90 | 91 | Logiciel Initial: désigne le Logiciel sous sa forme de Code Source et 92 | éventuellement de Code Objet et le cas échéant sa documentation, dans 93 | leur état au moment de leur première diffusion sous les termes du Contrat. 94 | 95 | Logiciel Modifié: désigne le Logiciel modifié par au moins une 96 | Contribution. 97 | 98 | Code Source: désigne l'ensemble des instructions et des lignes de 99 | programme du Logiciel et auquel l'accès est nécessaire en vue de 100 | modifier le Logiciel. 101 | 102 | Code Objet: désigne les fichiers binaires issus de la compilation du 103 | Code Source. 104 | 105 | Titulaire: désigne le ou les détenteurs des droits patrimoniaux d'auteur 106 | sur le Logiciel Initial. 107 | 108 | Licencié: désigne le ou les utilisateurs du Logiciel ayant accepté le 109 | Contrat. 110 | 111 | Contributeur: désigne le Licencié auteur d'au moins une Contribution. 112 | 113 | Concédant: désigne le Titulaire ou toute personne physique ou morale 114 | distribuant le Logiciel sous le Contrat. 115 | 116 | Contribution: désigne l'ensemble des modifications, corrections, 117 | traductions, adaptations et/ou nouvelles fonctionnalités intégrées dans 118 | le Logiciel par tout Contributeur, ainsi que tout Module Interne. 119 | 120 | Module: désigne un ensemble de fichiers sources y compris leur 121 | documentation qui permet de réaliser des fonctionnalités ou services 122 | supplémentaires à ceux fournis par le Logiciel. 123 | 124 | Module Externe: désigne tout Module, non dérivé du Logiciel, tel que ce 125 | Module et le Logiciel s'exécutent dans des espaces d'adressage 126 | différents, l'un appelant l'autre au moment de leur exécution. 127 | 128 | Module Interne: désigne tout Module lié au Logiciel de telle sorte 129 | qu'ils s'exécutent dans le même espace d'adressage. 130 | 131 | GNU GPL: désigne la GNU General Public License dans sa version 2 ou 132 | toute version ultérieure, telle que publiée par Free Software Foundation 133 | Inc. 134 | 135 | GNU Affero GPL: désigne la GNU Affero General Public License dans sa 136 | version 3 ou toute version ultérieure, telle que publiée par Free 137 | Software Foundation Inc. 138 | 139 | EUPL: désigne la Licence Publique de l'Union européenne dans sa version 140 | 1.1 ou toute version ultérieure, telle que publiée par la Commission 141 | Européenne. 142 | 143 | Parties: désigne collectivement le Licencié et le Concédant. 144 | 145 | Ces termes s'entendent au singulier comme au pluriel. 146 | 147 | 148 | Article 2 - OBJET 149 | 150 | Le Contrat a pour objet la concession par le Concédant au Licencié d'une 151 | licence non exclusive, cessible et mondiale du Logiciel telle que 152 | définie ci-après à l'article 5 <#etendue> pour toute la durée de 153 | protection des droits portant sur ce Logiciel. 154 | 155 | 156 | Article 3 - ACCEPTATION 157 | 158 | 3.1 L'acceptation par le Licencié des termes du Contrat est réputée 159 | acquise du fait du premier des faits suivants: 160 | 161 | * (i) le chargement du Logiciel par tout moyen notamment par 162 | téléchargement à partir d'un serveur distant ou par chargement à 163 | partir d'un support physique; 164 | * (ii) le premier exercice par le Licencié de l'un quelconque des 165 | droits concédés par le Contrat. 166 | 167 | 3.2 Un exemplaire du Contrat, contenant notamment un avertissement 168 | relatif aux spécificités du Logiciel, à la restriction de garantie et à 169 | la limitation à un usage par des utilisateurs expérimentés a été mis à 170 | disposition du Licencié préalablement à son acceptation telle que 171 | définie à l'article 3.1 <#acceptation-acquise> ci dessus et le Licencié 172 | reconnaît en avoir pris connaissance. 173 | 174 | 175 | Article 4 - ENTREE EN VIGUEUR ET DUREE 176 | 177 | 178 | 4.1 ENTREE EN VIGUEUR 179 | 180 | Le Contrat entre en vigueur à la date de son acceptation par le Licencié 181 | telle que définie en 3.1 <#acceptation-acquise>. 182 | 183 | 184 | 4.2 DUREE 185 | 186 | Le Contrat produira ses effets pendant toute la durée légale de 187 | protection des droits patrimoniaux portant sur le Logiciel. 188 | 189 | 190 | Article 5 - ETENDUE DES DROITS CONCEDES 191 | 192 | Le Concédant concède au Licencié, qui accepte, les droits suivants sur 193 | le Logiciel pour toutes destinations et pour la durée du Contrat dans 194 | les conditions ci-après détaillées. 195 | 196 | Par ailleurs, si le Concédant détient ou venait à détenir un ou 197 | plusieurs brevets d'invention protégeant tout ou partie des 198 | fonctionnalités du Logiciel ou de ses composants, il s'engage à ne pas 199 | opposer les éventuels droits conférés par ces brevets aux Licenciés 200 | successifs qui utiliseraient, exploiteraient ou modifieraient le 201 | Logiciel. En cas de cession de ces brevets, le Concédant s'engage à 202 | faire reprendre les obligations du présent alinéa aux cessionnaires. 203 | 204 | 205 | 5.1 DROIT D'UTILISATION 206 | 207 | Le Licencié est autorisé à utiliser le Logiciel, sans restriction quant 208 | aux domaines d'application, étant ci-après précisé que cela comporte: 209 | 210 | 1. 211 | 212 | la reproduction permanente ou provisoire du Logiciel en tout ou 213 | partie par tout moyen et sous toute forme. 214 | 215 | 2. 216 | 217 | le chargement, l'affichage, l'exécution, ou le stockage du Logiciel 218 | sur tout support. 219 | 220 | 3. 221 | 222 | la possibilité d'en observer, d'en étudier, ou d'en tester le 223 | fonctionnement afin de déterminer les idées et principes qui sont à 224 | la base de n'importe quel élément de ce Logiciel; et ceci, lorsque 225 | le Licencié effectue toute opération de chargement, d'affichage, 226 | d'exécution, de transmission ou de stockage du Logiciel qu'il est en 227 | droit d'effectuer en vertu du Contrat. 228 | 229 | 230 | 5.2 DROIT D'APPORTER DES CONTRIBUTIONS 231 | 232 | Le droit d'apporter des Contributions comporte le droit de traduire, 233 | d'adapter, d'arranger ou d'apporter toute autre modification au Logiciel 234 | et le droit de reproduire le logiciel en résultant. 235 | 236 | Le Licencié est autorisé à apporter toute Contribution au Logiciel sous 237 | réserve de mentionner, de façon explicite, son nom en tant qu'auteur de 238 | cette Contribution et la date de création de celle-ci. 239 | 240 | 241 | 5.3 DROIT DE DISTRIBUTION 242 | 243 | Le droit de distribution comporte notamment le droit de diffuser, de 244 | transmettre et de communiquer le Logiciel au public sur tout support et 245 | par tout moyen ainsi que le droit de mettre sur le marché à titre 246 | onéreux ou gratuit, un ou des exemplaires du Logiciel par tout procédé. 247 | 248 | Le Licencié est autorisé à distribuer des copies du Logiciel, modifié ou 249 | non, à des tiers dans les conditions ci-après détaillées. 250 | 251 | 252 | 5.3.1 DISTRIBUTION DU LOGICIEL SANS MODIFICATION 253 | 254 | Le Licencié est autorisé à distribuer des copies conformes du Logiciel, 255 | sous forme de Code Source ou de Code Objet, à condition que cette 256 | distribution respecte les dispositions du Contrat dans leur totalité et 257 | soit accompagnée: 258 | 259 | 1. 260 | 261 | d'un exemplaire du Contrat, 262 | 263 | 2. 264 | 265 | d'un avertissement relatif à la restriction de garantie et de 266 | responsabilité du Concédant telle que prévue aux articles 8 267 | <#responsabilite> et 9 <#garantie>, 268 | 269 | et que, dans le cas où seul le Code Objet du Logiciel est redistribué, 270 | le Licencié permette un accès effectif au Code Source complet du 271 | Logiciel pour une durée d'au moins 3 ans à compter de la distribution du 272 | logiciel, étant entendu que le coût additionnel d'acquisition du Code 273 | Source ne devra pas excéder le simple coût de transfert des données. 274 | 275 | 276 | 5.3.2 DISTRIBUTION DU LOGICIEL MODIFIE 277 | 278 | Lorsque le Licencié apporte une Contribution au Logiciel, les conditions 279 | de distribution du Logiciel Modifié en résultant sont alors soumises à 280 | l'intégralité des dispositions du Contrat. 281 | 282 | Le Licencié est autorisé à distribuer le Logiciel Modifié, sous forme de 283 | code source ou de code objet, à condition que cette distribution 284 | respecte les dispositions du Contrat dans leur totalité et soit 285 | accompagnée: 286 | 287 | 1. 288 | 289 | d'un exemplaire du Contrat, 290 | 291 | 2. 292 | 293 | d'un avertissement relatif à la restriction de garantie et de 294 | responsabilité du Concédant telle que prévue aux articles 8 295 | <#responsabilite> et 9 <#garantie>, 296 | 297 | et, dans le cas où seul le code objet du Logiciel Modifié est redistribué, 298 | 299 | 3. 300 | 301 | d'une note précisant les conditions d'accès effectif au code source 302 | complet du Logiciel Modifié, pendant une période d'au moins 3 ans à 303 | compter de la distribution du Logiciel Modifié, étant entendu que le 304 | coût additionnel d'acquisition du code source ne devra pas excéder 305 | le simple coût de transfert des données. 306 | 307 | 308 | 5.3.3 DISTRIBUTION DES MODULES EXTERNES 309 | 310 | Lorsque le Licencié a développé un Module Externe les conditions du 311 | Contrat ne s'appliquent pas à ce Module Externe, qui peut être distribué 312 | sous un contrat de licence différent. 313 | 314 | 315 | 5.3.4 COMPATIBILITE AVEC D'AUTRES LICENCES 316 | 317 | Le Licencié peut inclure un code soumis aux dispositions d'une des 318 | versions de la licence GNU GPL, GNU Affero GPL et/ou EUPL dans le 319 | Logiciel modifié ou non et distribuer l'ensemble sous les conditions de 320 | la même version de la licence GNU GPL, GNU Affero GPL et/ou EUPL. 321 | 322 | Le Licencié peut inclure le Logiciel modifié ou non dans un code soumis 323 | aux dispositions d'une des versions de la licence GNU GPL, GNU Affero 324 | GPL et/ou EUPL et distribuer l'ensemble sous les conditions de la même 325 | version de la licence GNU GPL, GNU Affero GPL et/ou EUPL. 326 | 327 | 328 | Article 6 - PROPRIETE INTELLECTUELLE 329 | 330 | 331 | 6.1 SUR LE LOGICIEL INITIAL 332 | 333 | Le Titulaire est détenteur des droits patrimoniaux sur le Logiciel 334 | Initial. Toute utilisation du Logiciel Initial est soumise au respect 335 | des conditions dans lesquelles le Titulaire a choisi de diffuser son 336 | oeuvre et nul autre n'a la faculté de modifier les conditions de 337 | diffusion de ce Logiciel Initial. 338 | 339 | Le Titulaire s'engage à ce que le Logiciel Initial reste au moins régi 340 | par le Contrat et ce, pour la durée visée à l'article 4.2 <#duree>. 341 | 342 | 343 | 6.2 SUR LES CONTRIBUTIONS 344 | 345 | Le Licencié qui a développé une Contribution est titulaire sur celle-ci 346 | des droits de propriété intellectuelle dans les conditions définies par 347 | la législation applicable. 348 | 349 | 350 | 6.3 SUR LES MODULES EXTERNES 351 | 352 | Le Licencié qui a développé un Module Externe est titulaire sur celui-ci 353 | des droits de propriété intellectuelle dans les conditions définies par 354 | la législation applicable et reste libre du choix du contrat régissant 355 | sa diffusion. 356 | 357 | 358 | 6.4 DISPOSITIONS COMMUNES 359 | 360 | Le Licencié s'engage expressément: 361 | 362 | 1. 363 | 364 | à ne pas supprimer ou modifier de quelque manière que ce soit les 365 | mentions de propriété intellectuelle apposées sur le Logiciel; 366 | 367 | 2. 368 | 369 | à reproduire à l'identique lesdites mentions de propriété 370 | intellectuelle sur les copies du Logiciel modifié ou non. 371 | 372 | Le Licencié s'engage à ne pas porter atteinte, directement ou 373 | indirectement, aux droits de propriété intellectuelle du Titulaire et/ou 374 | des Contributeurs sur le Logiciel et à prendre, le cas échéant, à 375 | l'égard de son personnel toutes les mesures nécessaires pour assurer le 376 | respect des dits droits de propriété intellectuelle du Titulaire et/ou 377 | des Contributeurs. 378 | 379 | 380 | Article 7 - SERVICES ASSOCIES 381 | 382 | 7.1 Le Contrat n'oblige en aucun cas le Concédant à la réalisation de 383 | prestations d'assistance technique ou de maintenance du Logiciel. 384 | 385 | Cependant le Concédant reste libre de proposer ce type de services. Les 386 | termes et conditions d'une telle assistance technique et/ou d'une telle 387 | maintenance seront alors déterminés dans un acte séparé. Ces actes de 388 | maintenance et/ou assistance technique n'engageront que la seule 389 | responsabilité du Concédant qui les propose. 390 | 391 | 7.2 De même, tout Concédant est libre de proposer, sous sa seule 392 | responsabilité, à ses licenciés une garantie, qui n'engagera que lui, 393 | lors de la redistribution du Logiciel et/ou du Logiciel Modifié et ce, 394 | dans les conditions qu'il souhaite. Cette garantie et les modalités 395 | financières de son application feront l'objet d'un acte séparé entre le 396 | Concédant et le Licencié. 397 | 398 | 399 | Article 8 - RESPONSABILITE 400 | 401 | 8.1 Sous réserve des dispositions de l'article 8.2 402 | <#limite-responsabilite>, le Licencié a la faculté, sous réserve de 403 | prouver la faute du Concédant concerné, de solliciter la réparation du 404 | préjudice direct qu'il subirait du fait du Logiciel et dont il apportera 405 | la preuve. 406 | 407 | 8.2 La responsabilité du Concédant est limitée aux engagements pris en 408 | application du Contrat et ne saurait être engagée en raison notamment: 409 | (i) des dommages dus à l'inexécution, totale ou partielle, de ses 410 | obligations par le Licencié, (ii) des dommages directs ou indirects 411 | découlant de l'utilisation ou des performances du Logiciel subis par le 412 | Licencié et (iii) plus généralement d'un quelconque dommage indirect. En 413 | particulier, les Parties conviennent expressément que tout préjudice 414 | financier ou commercial (par exemple perte de données, perte de 415 | bénéfices, perte d'exploitation, perte de clientèle ou de commandes, 416 | manque à gagner, trouble commercial quelconque) ou toute action dirigée 417 | contre le Licencié par un tiers, constitue un dommage indirect et 418 | n'ouvre pas droit à réparation par le Concédant. 419 | 420 | 421 | Article 9 - GARANTIE 422 | 423 | 9.1 Le Licencié reconnaît que l'état actuel des connaissances 424 | scientifiques et techniques au moment de la mise en circulation du 425 | Logiciel ne permet pas d'en tester et d'en vérifier toutes les 426 | utilisations ni de détecter l'existence d'éventuels défauts. L'attention 427 | du Licencié a été attirée sur ce point sur les risques associés au 428 | chargement, à l'utilisation, la modification et/ou au développement et à 429 | la reproduction du Logiciel qui sont réservés à des utilisateurs avertis. 430 | 431 | Il relève de la responsabilité du Licencié de contrôler, par tous 432 | moyens, l'adéquation du produit à ses besoins, son bon fonctionnement et 433 | de s'assurer qu'il ne causera pas de dommages aux personnes et aux biens. 434 | 435 | 9.2 Le Concédant déclare de bonne foi être en droit de concéder 436 | l'ensemble des droits attachés au Logiciel (comprenant notamment les 437 | droits visés à l'article 5 <#etendue>). 438 | 439 | 9.3 Le Licencié reconnaît que le Logiciel est fourni "en l'état" par le 440 | Concédant sans autre garantie, expresse ou tacite, que celle prévue à 441 | l'article 9.2 <#bonne-foi> et notamment sans aucune garantie sur sa 442 | valeur commerciale, son caractère sécurisé, innovant ou pertinent. 443 | 444 | En particulier, le Concédant ne garantit pas que le Logiciel est exempt 445 | d'erreur, qu'il fonctionnera sans interruption, qu'il sera compatible 446 | avec l'équipement du Licencié et sa configuration logicielle ni qu'il 447 | remplira les besoins du Licencié. 448 | 449 | 9.4 Le Concédant ne garantit pas, de manière expresse ou tacite, que le 450 | Logiciel ne porte pas atteinte à un quelconque droit de propriété 451 | intellectuelle d'un tiers portant sur un brevet, un logiciel ou sur tout 452 | autre droit de propriété. Ainsi, le Concédant exclut toute garantie au 453 | profit du Licencié contre les actions en contrefaçon qui pourraient être 454 | diligentées au titre de l'utilisation, de la modification, et de la 455 | redistribution du Logiciel. Néanmoins, si de telles actions sont 456 | exercées contre le Licencié, le Concédant lui apportera son expertise 457 | technique et juridique pour sa défense. Cette expertise technique et 458 | juridique est déterminée au cas par cas entre le Concédant concerné et 459 | le Licencié dans le cadre d'un protocole d'accord. Le Concédant dégage 460 | toute responsabilité quant à l'utilisation de la dénomination du 461 | Logiciel par le Licencié. Aucune garantie n'est apportée quant à 462 | l'existence de droits antérieurs sur le nom du Logiciel et sur 463 | l'existence d'une marque. 464 | 465 | 466 | Article 10 - RESILIATION 467 | 468 | 10.1 En cas de manquement par le Licencié aux obligations mises à sa 469 | charge par le Contrat, le Concédant pourra résilier de plein droit le 470 | Contrat trente (30) jours après notification adressée au Licencié et 471 | restée sans effet. 472 | 473 | 10.2 Le Licencié dont le Contrat est résilié n'est plus autorisé à 474 | utiliser, modifier ou distribuer le Logiciel. Cependant, toutes les 475 | licences qu'il aura concédées antérieurement à la résiliation du Contrat 476 | resteront valides sous réserve qu'elles aient été effectuées en 477 | conformité avec le Contrat. 478 | 479 | 480 | Article 11 - DISPOSITIONS DIVERSES 481 | 482 | 483 | 11.1 CAUSE EXTERIEURE 484 | 485 | Aucune des Parties ne sera responsable d'un retard ou d'une défaillance 486 | d'exécution du Contrat qui serait dû à un cas de force majeure, un cas 487 | fortuit ou une cause extérieure, telle que, notamment, le mauvais 488 | fonctionnement ou les interruptions du réseau électrique ou de 489 | télécommunication, la paralysie du réseau liée à une attaque 490 | informatique, l'intervention des autorités gouvernementales, les 491 | catastrophes naturelles, les dégâts des eaux, les tremblements de terre, 492 | le feu, les explosions, les grèves et les conflits sociaux, l'état de 493 | guerre... 494 | 495 | 11.2 Le fait, par l'une ou l'autre des Parties, d'omettre en une ou 496 | plusieurs occasions de se prévaloir d'une ou plusieurs dispositions du 497 | Contrat, ne pourra en aucun cas impliquer renonciation par la Partie 498 | intéressée à s'en prévaloir ultérieurement. 499 | 500 | 11.3 Le Contrat annule et remplace toute convention antérieure, écrite 501 | ou orale, entre les Parties sur le même objet et constitue l'accord 502 | entier entre les Parties sur cet objet. Aucune addition ou modification 503 | aux termes du Contrat n'aura d'effet à l'égard des Parties à moins 504 | d'être faite par écrit et signée par leurs représentants dûment habilités. 505 | 506 | 11.4 Dans l'hypothèse où une ou plusieurs des dispositions du Contrat 507 | s'avèrerait contraire à une loi ou à un texte applicable, existants ou 508 | futurs, cette loi ou ce texte prévaudrait, et les Parties feraient les 509 | amendements nécessaires pour se conformer à cette loi ou à ce texte. 510 | Toutes les autres dispositions resteront en vigueur. De même, la 511 | nullité, pour quelque raison que ce soit, d'une des dispositions du 512 | Contrat ne saurait entraîner la nullité de l'ensemble du Contrat. 513 | 514 | 515 | 11.5 LANGUE 516 | 517 | Le Contrat est rédigé en langue française et en langue anglaise, ces 518 | deux versions faisant également foi. 519 | 520 | 521 | Article 12 - NOUVELLES VERSIONS DU CONTRAT 522 | 523 | 12.1 Toute personne est autorisée à copier et distribuer des copies de 524 | ce Contrat. 525 | 526 | 12.2 Afin d'en préserver la cohérence, le texte du Contrat est protégé 527 | et ne peut être modifié que par les auteurs de la licence, lesquels se 528 | réservent le droit de publier périodiquement des mises à jour ou de 529 | nouvelles versions du Contrat, qui posséderont chacune un numéro 530 | distinct. Ces versions ultérieures seront susceptibles de prendre en 531 | compte de nouvelles problématiques rencontrées par les logiciels libres. 532 | 533 | 12.3 Tout Logiciel diffusé sous une version donnée du Contrat ne pourra 534 | faire l'objet d'une diffusion ultérieure que sous la même version du 535 | Contrat ou une version postérieure, sous réserve des dispositions de 536 | l'article 5.3.4 <#compatibilite>. 537 | 538 | 539 | Article 13 - LOI APPLICABLE ET COMPETENCE TERRITORIALE 540 | 541 | 13.1 Le Contrat est régi par la loi française. Les Parties conviennent 542 | de tenter de régler à l'amiable les différends ou litiges qui 543 | viendraient à se produire par suite ou à l'occasion du Contrat. 544 | 545 | 13.2 A défaut d'accord amiable dans un délai de deux (2) mois à compter 546 | de leur survenance et sauf situation relevant d'une procédure d'urgence, 547 | les différends ou litiges seront portés par la Partie la plus diligente 548 | devant les Tribunaux compétents de Paris. 549 | 550 | 551 | --------------------------------------------------------------------------------