boolforge 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
boolforge/__init__.py ADDED
@@ -0,0 +1,67 @@
1
+ # Core public classes
2
+ from .boolean_function import (
3
+ BooleanFunction,
4
+ display_truth_table,
5
+ get_layer_structure_from_canalized_outputs
6
+ )
7
+ from .boolean_network import (
8
+ BooleanNetwork,
9
+ get_entropy_of_basin_size_distribution
10
+ )
11
+ from .wiring_diagram import WiringDiagram
12
+
13
+ # Canonical public generators
14
+ from .generate import (
15
+ random_function,
16
+ random_NCF,
17
+ random_k_canalizing_function,
18
+ random_wiring_diagram,
19
+ random_network,
20
+ random_null_model,
21
+ )
22
+
23
+ from .utils import (
24
+ bin2dec,
25
+ dec2bin,
26
+ get_left_side_of_truth_table,
27
+ hamming_weight_to_ncf_layer_structure,
28
+ )
29
+
30
+ from .modularity import (
31
+ compress_trajectories,
32
+ product_of_trajectories,
33
+ plot_trajectory,
34
+ )
35
+
36
+ from .bio_models import get_bio_models_from_repository
37
+
38
+ # Version
39
+ try:
40
+ from ._version import __version__
41
+ except ImportError:
42
+ __version__ = "unknown"
43
+
44
+ __all__ = [
45
+ "bin2dec",
46
+ "dec2bin",
47
+ "get_left_side_of_truth_table",
48
+ "hamming_weight_to_ncf_layer_structure",
49
+ "BooleanFunction",
50
+ "display_truth_table",
51
+ "get_layer_structure_from_canalized_outputs",
52
+ "BooleanNetwork",
53
+ "get_entropy_of_basin_size_distribution",
54
+ "WiringDiagram",
55
+ "random_function",
56
+ "random_NCF",
57
+ "random_k_canalizing_function",
58
+ "random_wiring_diagram",
59
+ "random_network",
60
+ "random_null_model",
61
+ "get_bio_models_from_repository",
62
+ "__version__",
63
+
64
+ "compress_trajectories",
65
+ "product_of_trajectories",
66
+ "plot_trajectory"
67
+ ]
boolforge/_version.py ADDED
@@ -0,0 +1 @@
1
+ __version__ = '1.0.0'
@@ -0,0 +1,380 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ r"""
4
+ This module provides functionality for retrieving, parsing, and loading
5
+ biological Boolean network models from public online repositories.
6
+
7
+ The :mod:`boolforge.bio_models` module allows users to programmatically access
8
+ and import published Boolean and logical gene regulatory network models from
9
+ GitHub repositories such as:
10
+
11
+ - expert-curated (ckadelka): manually curated models from the
12
+ Design Principles of Gene Regulatory Networks repository.
13
+
14
+ - pystablemotifs (jcrozum): models accompanying the PyStableMotifs library.
15
+
16
+ - biodivine (sybila): models from the Sybila Biodivine Boolean Models repository.
17
+
18
+ Functions are provided to:
19
+
20
+ - Recursively list and download files from GitHub folders using the REST API.
21
+ - Fetch raw text or byte content from remote sources.
22
+ - Parse Boolean network models into BooleanNetwork objects.
23
+ - Batch-download and convert all models from supported repositories.
24
+
25
+ This module is intended to facilitate reproducible research by providing
26
+ direct access to real-world Boolean GRN models for simulation, comparison,
27
+ and benchmarking.
28
+
29
+ Example
30
+ -------
31
+ >>> from boolforge import bio_models
32
+ >>> result = bio_models.get_bio_models_from_repository('')
33
+ >>> len(result['BooleanNetworks'])
34
+ 122
35
+ """
36
+
37
+ import pickle
38
+ import io
39
+
40
+ try:
41
+ import requests
42
+ except ImportError as e:
43
+ raise ImportError(
44
+ "The optional dependency 'requests' is required for bio_models. "
45
+ "Install it with `pip install requests`."
46
+ ) from e
47
+
48
+ from .boolean_network import BooleanNetwork
49
+
50
+ __all__ = [
51
+ 'load_model',
52
+ 'get_bio_models_from_repository',
53
+ ]
54
+
55
+ def _get_content_in_remote_folder(
56
+ url: str,
57
+ file_names: list,
58
+ file_download_urls: list
59
+ ) -> None:
60
+ """
61
+ Recursively collect file names and raw download URLs from a GitHub folder.
62
+
63
+ Parameters
64
+ ----------
65
+ url : str
66
+ GitHub API URL pointing to a repository folder.
67
+ file_names : list
68
+ List that will be populated with discovered file names.
69
+ file_download_urls : list
70
+ List that will be populated with corresponding raw download URLs.
71
+
72
+ Returns
73
+ -------
74
+ None
75
+ """
76
+ import logging
77
+
78
+ folder = requests.get(url)
79
+ folder.raise_for_status()
80
+ folder_json = folder.json()
81
+
82
+ for item in folder_json:
83
+ if item['size'] > 0 and item['download_url'] is not None:
84
+ file_names.append(item['name'])
85
+ file_download_urls.append(item['download_url'])
86
+ else:
87
+ try:
88
+ _get_content_in_remote_folder(
89
+ item['url'], file_names, file_download_urls
90
+ )
91
+ except Exception as e:
92
+ logging.warning(
93
+ "Failed to access subfolder at %s: %s",
94
+ item.get('url', '<unknown>'),
95
+ e,
96
+ )
97
+
98
+ def get_content_in_remote_folder(url: str) -> tuple:
99
+ """
100
+ Retrieve file names and raw download URLs from a GitHub repository folder.
101
+
102
+ Parameters
103
+ ----------
104
+ url : str
105
+ GitHub API URL pointing to a repository folder.
106
+
107
+ Returns
108
+ -------
109
+ tuple[list[str], list[str]]
110
+ A tuple ``(file_names, file_download_urls)``, where:
111
+
112
+ - ``file_names`` contains the names of discovered files.
113
+ - ``file_download_urls`` contains corresponding raw download URLs.
114
+ """
115
+ file_names = []
116
+ file_download_urls = []
117
+ _get_content_in_remote_folder(url, file_names, file_download_urls)
118
+ return file_names, file_download_urls
119
+
120
+
121
+ def fetch_file(download_url: str) -> str:
122
+ """
123
+ Download raw text content from a remote file.
124
+
125
+ Parameters
126
+ ----------
127
+ download_url : str
128
+ Direct download URL to the file.
129
+
130
+ Returns
131
+ -------
132
+ str
133
+ File content as plain text.
134
+ """
135
+ r = requests.get(download_url)
136
+ r.raise_for_status()
137
+ return r.text
138
+
139
+
140
+ def fetch_file_bytes(download_url: str) -> bytes:
141
+ """
142
+ Download raw binary content from a remote file.
143
+
144
+ Parameters
145
+ ----------
146
+ download_url : str
147
+ Direct download URL to the file.
148
+
149
+ Returns
150
+ -------
151
+ bytes
152
+ File content as raw bytes.
153
+ """
154
+ r = requests.get(download_url)
155
+ r.raise_for_status()
156
+ return r.content
157
+
158
+
159
+ def load_model(
160
+ download_url: str,
161
+ max_degree: int = 24,
162
+ possible_separators: list[str] = ['* =', '*=', '=', ','],
163
+ ignore_first_line: bool = False,
164
+ simplify_functions: bool = False,
165
+ ) -> BooleanNetwork:
166
+ """
167
+ Load and parse a Boolean network model from a remote text file.
168
+
169
+ Parameters
170
+ ----------
171
+ download_url : str
172
+ Direct download URL to the model file.
173
+ max_degree : int, optional
174
+ Maximum allowed in-degree for nodes (default: 24).
175
+ possible_separators : list[str], optional
176
+ Possible assignment separators used in the model file.
177
+ ignore_first_line : bool, optional
178
+ If True, skip the first line of the file (default: False).
179
+ simplify_functions : bool, optional
180
+ If True, Boolean update functions are simplified after initialization.
181
+ Default is False.
182
+
183
+ Returns
184
+ -------
185
+ BooleanNetwork
186
+ Parsed Boolean network.
187
+
188
+ Raises
189
+ ------
190
+ ValueError
191
+ If the model cannot be parsed.
192
+ """
193
+ string = fetch_file(download_url)
194
+
195
+ if ignore_first_line:
196
+ string = string[string.index('\n') + 1:]
197
+
198
+ errors = []
199
+
200
+ for sep in possible_separators:
201
+ try:
202
+ return BooleanNetwork.from_string(
203
+ string,
204
+ separator=sep,
205
+ max_degree=max_degree,
206
+ simplify_functions=simplify_functions,
207
+ )
208
+ except Exception as e:
209
+ errors.append((sep, e))
210
+
211
+ error_msg = "\n".join(
212
+ f"separator '{sep}' failed with: {repr(err)}"
213
+ for sep, err in errors
214
+ )
215
+
216
+ raise ValueError(
217
+ f"Failed to parse Boolean network model from {download_url} "
218
+ f"using any separator.\nAttempted separators: {possible_separators}\n"
219
+ f"Errors:\n{error_msg}"
220
+ )
221
+
222
+ def get_bio_models_from_repository(
223
+ repository: str = 'expert-curated (ckadelka)',
224
+ download_urls_pystablemotifs: list[str] | None = None,
225
+ max_degree: int = 24,
226
+ simplify_functions: bool = False,
227
+ ) -> dict:
228
+ """
229
+ Load Boolean network models from selected online repositories.
230
+
231
+ This function downloads, parses, and constructs Boolean network models
232
+ from several curated online repositories. Models that cannot be parsed
233
+ are skipped and recorded separately.
234
+
235
+ Parameters
236
+ ----------
237
+ repository : str, optional
238
+ Identifier of the source repository. Supported values are:
239
+
240
+ - 'expert-curated (ckadelka)' (default)
241
+ - 'pystablemotifs (jcrozum)'
242
+ - 'biodivine (sybila)'
243
+
244
+ download_urls_pystablemotifs : list[str] or None, optional
245
+ Optional list of direct download URLs for PyStableMotifs models.
246
+ If provided, these URLs are used instead of querying the GitHub API (faster).
247
+ If None (default), model URLs are fetched dynamically from GitHub.
248
+ max_degree : int, optional
249
+ Maximum allowed in-degree for nodes (default: 24).
250
+ simplify_functions : bool, optional
251
+ If True, Boolean update functions are simplified after initialization.
252
+ Default is False.
253
+
254
+ Returns
255
+ -------
256
+ dict
257
+ Dictionary with the following keys:
258
+
259
+ - 'BooleanNetworks' : list[BooleanNetwork]
260
+ List of successfully parsed Boolean network models.
261
+
262
+ - 'SuccessfulDownloadURLs' : list[str]
263
+ URLs corresponding to models that were successfully loaded.
264
+
265
+ - 'FailedDownloadURLs' : list[str]
266
+ URLs corresponding to models that could not be parsed or loaded.
267
+ """
268
+ repositories = [
269
+ 'expert-curated (ckadelka)',
270
+ 'pystablemotifs (jcrozum)',
271
+ 'biodivine (sybila)',
272
+ ]
273
+
274
+ bns = []
275
+ successful_download_urls = []
276
+ failed_download_urls = []
277
+
278
+ if repository == 'expert-curated (ckadelka)':
279
+ download_url_base = (
280
+ 'https://raw.githubusercontent.com/ckadelka/'
281
+ 'DesignPrinciplesGeneNetworks/main/'
282
+ 'update_rules_122_models_Kadelka_SciAdv/'
283
+ )
284
+ download_url = download_url_base + 'all_txt_files.csv'
285
+ csv = fetch_file(download_url)
286
+
287
+ for line in csv.splitlines():
288
+ download_url = download_url_base + line
289
+ if '.txt' in download_url:
290
+ try:
291
+ if 'tabular' in download_url:
292
+ F, I, var, constants = pickle.load(
293
+ io.BytesIO(fetch_file_bytes(download_url))
294
+ )
295
+ for i in range(len(constants)):
296
+ F.append([0, 1])
297
+ I.append([len(var) + i])
298
+ bn = BooleanNetwork(F,
299
+ I,
300
+ var + constants,
301
+ simplify_functions=simplify_functions)
302
+ else:
303
+ bn = load_model(
304
+ download_url,
305
+ possible_separators = ['='],
306
+ simplify_functions=simplify_functions,
307
+ max_degree = max_degree,
308
+ )
309
+
310
+ successful_download_urls.append(download_url)
311
+ bns.append(bn)
312
+
313
+ except Exception:
314
+ failed_download_urls.append(download_url)
315
+
316
+ elif repository == 'pystablemotifs (jcrozum)':
317
+ if download_urls_pystablemotifs is None:
318
+ url = "https://api.github.com/repos/jcrozum/pystablemotifs/contents/models"
319
+ _, download_urls = get_content_in_remote_folder(url)
320
+ else:
321
+ download_urls = download_urls_pystablemotifs
322
+
323
+ for download_url in download_urls:
324
+ if '.txt' in download_url:
325
+ try:
326
+ bn = load_model(
327
+ download_url,
328
+ possible_separators=['* =', '* =', '* =', '* =', '*='],
329
+ # original_and=[" and ", "&"],
330
+ # original_or=[" or ", "|"],
331
+ # original_not=[" not ", " !"],
332
+ simplify_functions=simplify_functions,
333
+ max_degree = max_degree,
334
+ )
335
+ successful_download_urls.append(download_url)
336
+ bns.append(bn)
337
+ except Exception:
338
+ failed_download_urls.append(download_url)
339
+
340
+ elif repository == 'biodivine (sybila)':
341
+ download_url_base = (
342
+ 'https://raw.githubusercontent.com/sybila/'
343
+ 'biodivine-boolean-models/main/models/'
344
+ )
345
+ download_url = download_url_base + 'summary.csv'
346
+ csv = fetch_file(download_url)
347
+
348
+ for line in csv.splitlines():
349
+ try:
350
+ ID, name, variables, inputs, regulations = line.split(', ')
351
+ download_url = (
352
+ download_url_base
353
+ + '[id-%s]__[var-%s]__[in-%s]__[%s]/model.bnet'
354
+ % (ID, variables, inputs, name)
355
+ )
356
+ bn = load_model(
357
+ download_url,
358
+ # original_and=" & ",
359
+ # original_or=" | ",
360
+ # original_not="!",
361
+ ignore_first_line=True,
362
+ simplify_functions=simplify_functions,
363
+ max_degree=max_degree,
364
+ )
365
+ successful_download_urls.append(download_url)
366
+ bns.append(bn)
367
+ except Exception:
368
+ failed_download_urls.append(download_url)
369
+
370
+ else:
371
+ raise ValueError(
372
+ "repository must be one of:\n - " + "\n - ".join(repositories)
373
+ )
374
+
375
+ return {
376
+ "BooleanNetworks": bns,
377
+ "SuccessfulDownloadURLs": successful_download_urls,
378
+ "FailedDownloadURLs": failed_download_urls,
379
+ }
380
+