csa3d 0.2.0__tar.gz → 0.2.1__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: csa3d
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: Extract and analyze enzyme catalytic sites in 3D from the Mechanism and Catalytic Site Atlas (M-CSA)
5
5
  Author-email: Ioannis Riziotis <riziotis@ebi.ac.uk>
6
6
  License: Apache-2.0
@@ -30,9 +30,10 @@ Requires-Dist: scipy>=1.7.0
30
30
  Requires-Dist: scikit-learn>=0.24.0
31
31
  Requires-Dist: rdkit>=2021.09.0
32
32
  Requires-Dist: natsort>=8.0.0
33
- Requires-Dist: dendrogram-cut>=0.1.0
34
- Requires-Dist: parity-core>=0.5.0
35
33
  Requires-Dist: rmsd>=1.4.0
34
+ Provides-Extra: clustering
35
+ Requires-Dist: matplotlib>=3.5; extra == "clustering"
36
+ Requires-Dist: plotly>=5.0; extra == "clustering"
36
37
  Provides-Extra: dev
37
38
  Requires-Dist: pytest>=7.0; extra == "dev"
38
39
  Requires-Dist: pytest-cov>=4.0; extra == "dev"
@@ -0,0 +1,7 @@
1
+ """Vendored dependencies for CSA-3D.
2
+
3
+ This package contains minimal implementations of research-specific dependencies
4
+ that are not available on PyPI.
5
+ """
6
+
7
+ __all__ = ['dendrogram_cut']
@@ -0,0 +1,469 @@
1
+ import itertools
2
+
3
+ import numpy as np
4
+ import scipy.spatial
5
+ import scipy.cluster
6
+ import matplotlib.cm
7
+ import plotly.graph_objs as go
8
+ from sklearn.metrics import silhouette_score
9
+
10
+
11
+ class DendrogramCut:
12
+ def __init__(self, k_max, method='average'):
13
+ self.k_max = k_max
14
+ self.method = method
15
+
16
+ def fit(self, distance_matrix):
17
+ '''
18
+ Build linkage_stats
19
+ css: cross sum of square when merging c1 and c2
20
+ tss: total sum of square of merged cluster
21
+ '''
22
+ # Handle both condensed and square distance matrices
23
+ if distance_matrix.ndim == 1:
24
+ self.distance_matrix = scipy.spatial.distance.squareform(distance_matrix)
25
+ self.linkage = scipy.cluster.hierarchy.linkage(distance_matrix, method=self.method, optimal_ordering=True)
26
+ else:
27
+ self.distance_matrix = distance_matrix
28
+ self.linkage = scipy.cluster.hierarchy.linkage(scipy.spatial.distance.pdist(distance_matrix), method=self.method, optimal_ordering=True)
29
+
30
+ self.n_data = self.distance_matrix.shape[0]
31
+ self.linkage_stats = [{'c1': 0, 'c2': 0, 'css': 0, 'tss': 0, 'indices': set()} for _ in range(2 * self.n_data - 1)]
32
+
33
+ for i in range(self.n_data):
34
+ self.linkage_stats[i]['c1'] = i
35
+ self.linkage_stats[i]['c2'] = i
36
+ self.linkage_stats[i]['indices'].add(i)
37
+
38
+ for i, (c1, c2, _, _) in enumerate(self.linkage):
39
+ c1 = int(c1)
40
+ c2 = int(c2)
41
+ self.linkage_stats[i + self.n_data]['c1'] = c1
42
+ self.linkage_stats[i + self.n_data]['c2'] = c2
43
+ self.linkage_stats[i + self.n_data]['indices'].update(self.linkage_stats[c1]['indices'])
44
+ self.linkage_stats[i + self.n_data]['indices'].update(self.linkage_stats[c2]['indices'])
45
+
46
+ for i in range(self.n_data, 2 * self.n_data - 1):
47
+ c1 = self.linkage_stats[i]['c1']
48
+ c2 = self.linkage_stats[i]['c2']
49
+ c1_indices = np.asarray(list(self.linkage_stats[c1]['indices']))
50
+ c2_indices = np.asarray(list(self.linkage_stats[c2]['indices']))
51
+
52
+ sample_distances = self.distance_matrix[c1_indices, :][:, c2_indices]
53
+ self.linkage_stats[i]['css'] = np.sum(sample_distances ** 2)
54
+ self.linkage_stats[i]['tss'] = self.linkage_stats[i]['css'] + self.linkage_stats[c1]['tss'] + self.linkage_stats[c2]['tss']
55
+
56
+ ### dynamic programming ###
57
+ '''
58
+ Dynamic programming
59
+ kl_mat[i, k]: the number of clusters in the left branch of linkage_stat[$i], maximal cluster $k
60
+ mss_mat[i, k]: the optimal mean square error achieved at linkage_stat[$i], maximal cluster $k
61
+ '''
62
+ self.kl_mat = np.zeros((self.n_data * 2 - 1, self.k_max + 1), dtype=int)
63
+ self.mss_mat = np.zeros((self.n_data * 2 - 1, self.k_max + 1), dtype=float) + np.inf
64
+
65
+ for i in range(self.n_data):
66
+ self.mss_mat[i, 1] = 0
67
+
68
+ for i in range(self.n_data, 2 * self.n_data - 1):
69
+ self.mss_mat[i, 1] = self.linkage_stats[i]['tss'] / len(self.linkage_stats[i]['indices'])
70
+
71
+ for i in range(self.n_data, 2 * self.n_data - 1):
72
+ for k in range(2, self.k_max + 1):
73
+ kl_min = 0
74
+ mss_min = np.inf
75
+ for kl in range(1, k):
76
+ tss = self.mss_mat[self.linkage_stats[i]['c1'], kl] + self.mss_mat[self.linkage_stats[i]['c2'], k - kl]
77
+ if tss < mss_min:
78
+ kl_min = kl
79
+ mss_min = tss
80
+ self.kl_mat[i, k] = kl_min
81
+ self.mss_mat[i, k] = mss_min
82
+
83
+ return self
84
+
85
+ def _get_cut_nodes(self, v, k):
86
+ if k == 1:
87
+ yield v
88
+ else:
89
+ yield from self._get_cut_nodes(self.linkage_stats[v]['c1'], self.kl_mat[v, k])
90
+ yield from self._get_cut_nodes(self.linkage_stats[v]['c2'], k - self.kl_mat[v, k])
91
+
92
+ def get_cluster_mss(self, k):
93
+ total_mss = 0.
94
+ for cid in self._get_cut_nodes(2 * self.n_data - 2, k):
95
+ total_mss += self.linkage_stats[cid]['tss'] / len(self.linkage_stats[cid]['indices'])
96
+
97
+ return total_mss
98
+
99
+ def get_cluster_label(self, k):
100
+ ### get flat clusters ###
101
+ z = np.zeros(self.n_data, dtype=int) - 1
102
+ for c, cid in enumerate(self._get_cut_nodes(2 * self.n_data - 2, k)):
103
+ for i in self.linkage_stats[cid]['indices']:
104
+ if z[i] != -1:
105
+ print(i)
106
+ z[i] = c
107
+
108
+ return z
109
+
110
+ def _dirichlet_process_kl(self, n_list, alpha_):
111
+ out = 0.
112
+ count = alpha_
113
+ for n in n_list:
114
+ out -= np.log(alpha_ / count)
115
+ count += 1
116
+ for i in range(1, n):
117
+ out -= np.log(i / count)
118
+ count += 1
119
+ return out
120
+
121
+ def pac_bayesian_cut(self, alpha_=1., lambda_=1.):
122
+ min_loss = np.inf
123
+ min_loss_k = None
124
+
125
+ for k in range(1, self.k_max + 1):
126
+ n_list = [len(self.linkage_stats[c]['indices']) for c in self._get_cut_nodes(2 * self.n_data - 2, k)]
127
+ total_mss = self.get_cluster_mss(k)
128
+ loss = total_mss + self._dirichlet_process_kl(n_list, alpha_) / lambda_
129
+ if loss < min_loss:
130
+ min_loss = loss
131
+ min_loss_k = k
132
+
133
+ return min_loss_k
134
+
135
+ def dendrogram_plot(self, k=None, label=None):
136
+ cmap = matplotlib.cm.get_cmap('viridis')
137
+
138
+ order = scipy.cluster.hierarchy.leaves_list(self.linkage)
139
+
140
+ dendrogram_stat = dict((v, {'x': i, 'y': 0}) for i, v in enumerate(order))
141
+
142
+ data = []
143
+
144
+ ### plot dendrogram ###
145
+ if k is None:
146
+ norm = np.max(self.linkage[:, 2])
147
+
148
+ for res in self.linkage:
149
+ new_cluster = len(dendrogram_stat)
150
+ dendrogram_stat[new_cluster] = {'x': (dendrogram_stat[res[0]]['x'] + dendrogram_stat[res[1]]['x']) / 2, 'y': res[2]}
151
+ x_range = np.array([dendrogram_stat[res[0]]['x'], dendrogram_stat[res[0]]['x'], dendrogram_stat[res[1]]['x'], dendrogram_stat[res[1]]['x']])
152
+ y_range = np.array([dendrogram_stat[res[0]]['y'], dendrogram_stat[new_cluster]['y'], dendrogram_stat[new_cluster]['y'], dendrogram_stat[res[1]]['y']])
153
+
154
+ data.append(
155
+ {
156
+ 'hoverinfo': 'text',
157
+ 'marker': {'color': 'rgb({}, {}, {})'.format(*cmap(res[2] / norm, bytes=True)[:3])},
158
+ 'mode': 'lines',
159
+ 'type': 'scatter',
160
+ 'x': x_range,
161
+ 'xaxis': 'x',
162
+ 'y': y_range,
163
+ 'yaxis': 'y'
164
+ }
165
+ )
166
+ else:
167
+ pivot = set(self._get_cut_nodes(2 * self.n_data - 2, k))
168
+ z = self.get_cluster_label(k)
169
+ colorscale = dict((i, z[i] / (k - 1)) for i in range(self.n_data))
170
+
171
+ for res in self.linkage:
172
+ new_cluster = len(dendrogram_stat)
173
+ colorscale[new_cluster] = (colorscale[int(res[0])] + colorscale[int(res[1])]) / 2
174
+ dendrogram_stat[new_cluster] = {'x': (dendrogram_stat[res[0]]['x'] + dendrogram_stat[res[1]]['x']) / 2, 'y': res[2]}
175
+ x_range = np.array([dendrogram_stat[res[0]]['x'], dendrogram_stat[res[0]]['x'], dendrogram_stat[res[1]]['x'], dendrogram_stat[res[1]]['x']])
176
+ y_range = np.array([dendrogram_stat[res[0]]['y'], dendrogram_stat[new_cluster]['y'], dendrogram_stat[new_cluster]['y'], dendrogram_stat[res[1]]['y']])
177
+
178
+ if new_cluster in pivot:
179
+ data.append(
180
+ {
181
+ 'x': [dendrogram_stat[new_cluster]['x']],
182
+ 'y': [dendrogram_stat[new_cluster]['y']],
183
+ 'type': 'scatter',
184
+ 'mode': 'markers',
185
+ 'xaxis': 'x',
186
+ 'yaxis': 'y',
187
+ 'marker': {'size': [20], 'color': 'rgb({}, {}, {})'.format(*cmap(colorscale[new_cluster], bytes=True)[:3])}
188
+ }
189
+ )
190
+
191
+ data.append(
192
+ {
193
+ 'hoverinfo': 'text',
194
+ 'marker': {'color': 'rgb({}, {}, {})'.format(*cmap(colorscale[new_cluster], bytes=True)[:3])},
195
+ 'mode': 'lines',
196
+ 'type': 'scatter',
197
+ 'x': x_range,
198
+ 'xaxis': 'x',
199
+ 'y': y_range,
200
+ 'yaxis': 'y'
201
+ }
202
+ )
203
+
204
+ fig = go.Figure(data=data)
205
+
206
+ ### edit layout ###
207
+ if label is not None:
208
+ fig.update_layout(
209
+ {
210
+ 'xaxis': {
211
+ 'tickmode': 'array',
212
+ 'tickvals': np.arange(len(label)),
213
+ 'ticktext': label[order]
214
+ }
215
+ }
216
+ )
217
+
218
+ fig.update_layout(
219
+ {
220
+ 'width': 800,
221
+ 'height': 800,
222
+ 'showlegend': False,
223
+ 'hovermode': 'closest',
224
+ 'xaxis': {
225
+ 'domain': [0, 1],
226
+ 'rangemode': 'tozero',
227
+ 'mirror': False,
228
+ 'showgrid': False,
229
+ 'showline': False,
230
+ 'zeroline': False,
231
+ 'showticklabels': True,
232
+ 'ticks': ""
233
+ },
234
+ 'yaxis': {
235
+ 'domain': [0, 1],
236
+ 'rangemode': 'tozero',
237
+ 'mirror': False,
238
+ 'showgrid': False,
239
+ 'showline': False,
240
+ 'zeroline': False,
241
+ 'showticklabels': True,
242
+ 'ticks': ""
243
+ },
244
+ 'paper_bgcolor': 'rgba(0,0,0,0)',
245
+ 'plot_bgcolor': 'rgba(0,0,0,0)'
246
+ }
247
+ )
248
+
249
+ fig.show()
250
+
251
+ def heatmap_with_dendrogram_plot(self, k=None, label=None):
252
+ cmap = matplotlib.cm.get_cmap('viridis')
253
+
254
+ order = scipy.cluster.hierarchy.leaves_list(self.linkage)
255
+
256
+ dendrogram_stat = dict((v, {'x': i, 'y': 0}) for i, v in enumerate(order))
257
+
258
+ data = []
259
+
260
+ ### plot dendrogram ###
261
+ if k is None:
262
+ norm = np.max(self.linkage[:, 2])
263
+
264
+ for res in self.linkage:
265
+ new_cluster = len(dendrogram_stat)
266
+ dendrogram_stat[new_cluster] = {'x': (dendrogram_stat[res[0]]['x'] + dendrogram_stat[res[1]]['x']) / 2, 'y': res[2]}
267
+ x_range = np.array([dendrogram_stat[res[0]]['x'], dendrogram_stat[res[0]]['x'], dendrogram_stat[res[1]]['x'], dendrogram_stat[res[1]]['x']])
268
+ y_range = np.array([dendrogram_stat[res[0]]['y'], dendrogram_stat[new_cluster]['y'], dendrogram_stat[new_cluster]['y'], dendrogram_stat[res[1]]['y']])
269
+
270
+ data.append(
271
+ {
272
+ 'hoverinfo': 'text',
273
+ 'marker': {'color': 'rgb({}, {}, {})'.format(*cmap(res[2] / norm, bytes=True)[:3])},
274
+ 'mode': 'lines',
275
+ 'type': 'scatter',
276
+ 'x': x_range,
277
+ 'xaxis': 'x',
278
+ 'y': y_range,
279
+ 'yaxis': 'y2'
280
+ }
281
+ )
282
+
283
+ data.append(
284
+ {
285
+ 'hoverinfo': 'text',
286
+ 'marker': {'color': 'rgb({}, {}, {})'.format(*cmap(res[2] / norm, bytes=True)[:3])},
287
+ 'mode': 'lines',
288
+ 'type': 'scatter',
289
+ 'x': -y_range,
290
+ 'xaxis': 'x2',
291
+ 'y': x_range,
292
+ 'yaxis': 'y'
293
+ }
294
+ )
295
+ else:
296
+ pivot = set(self._get_cut_nodes(2 * self.n_data - 2, k))
297
+ z = self.get_cluster_label(k)
298
+ colorscale = dict((i, z[i] / (k - 1)) for i in range(self.n_data))
299
+
300
+ for res in self.linkage:
301
+ new_cluster = len(dendrogram_stat)
302
+ colorscale[new_cluster] = (colorscale[int(res[0])] + colorscale[int(res[1])]) / 2
303
+ dendrogram_stat[new_cluster] = {'x': (dendrogram_stat[res[0]]['x'] + dendrogram_stat[res[1]]['x']) / 2, 'y': res[2]}
304
+ x_range = np.array([dendrogram_stat[res[0]]['x'], dendrogram_stat[res[0]]['x'], dendrogram_stat[res[1]]['x'], dendrogram_stat[res[1]]['x']])
305
+ y_range = np.array([dendrogram_stat[res[0]]['y'], dendrogram_stat[new_cluster]['y'], dendrogram_stat[new_cluster]['y'], dendrogram_stat[res[1]]['y']])
306
+
307
+ if new_cluster in pivot:
308
+ data.append(
309
+ {
310
+ 'x': [dendrogram_stat[new_cluster]['x']],
311
+ 'y': [dendrogram_stat[new_cluster]['y']],
312
+ 'type': 'scatter',
313
+ 'mode': 'markers',
314
+ 'xaxis': 'x',
315
+ 'yaxis': 'y2',
316
+ 'marker': {'size': [10], 'color': 'rgb({}, {}, {})'.format(*cmap(colorscale[new_cluster], bytes=True)[:3])}
317
+ }
318
+ )
319
+ data.append(
320
+ {
321
+ 'x': [-dendrogram_stat[new_cluster]['y']],
322
+ 'y': [dendrogram_stat[new_cluster]['x']],
323
+ 'type': 'scatter',
324
+ 'mode': 'markers',
325
+ 'xaxis': 'x2',
326
+ 'yaxis': 'y',
327
+ 'marker': {'size': [10], 'color': 'rgb({}, {}, {})'.format(*cmap(colorscale[new_cluster], bytes=True)[:3])}
328
+ }
329
+ )
330
+
331
+ data.append(
332
+ {
333
+ 'hoverinfo': 'text',
334
+ 'marker': {'color': 'rgb({}, {}, {})'.format(*cmap(colorscale[new_cluster], bytes=True)[:3])},
335
+ 'mode': 'lines',
336
+ 'type': 'scatter',
337
+ 'x': x_range,
338
+ 'xaxis': 'x',
339
+ 'y': y_range,
340
+ 'yaxis': 'y2'
341
+ }
342
+ )
343
+
344
+ data.append(
345
+ {
346
+ 'hoverinfo': 'text',
347
+ 'marker': {'color': 'rgb({}, {}, {})'.format(*cmap(colorscale[new_cluster], bytes=True)[:3])},
348
+ 'mode': 'lines',
349
+ 'type': 'scatter',
350
+ 'x': -y_range,
351
+ 'xaxis': 'x2',
352
+ 'y': x_range,
353
+ 'yaxis': 'y'
354
+ }
355
+ )
356
+
357
+ ### plot hard cluster region ###
358
+
359
+ ordered_z = z[order]
360
+ block_size = [sum(1 for __ in g) for _, g in itertools.groupby(ordered_z)]
361
+ start = -0.5
362
+ for i in range(len(block_size)):
363
+ data.append(
364
+ go.Scatter(
365
+ x=[start, start, start+block_size[i], start+block_size[i], start],
366
+ y=[start, start+block_size[i], start+block_size[i], start, start],
367
+ mode='lines',
368
+ hoverinfo='none',
369
+ line=dict(color='#FF0000')
370
+ )
371
+ )
372
+ start += block_size[i]
373
+
374
+ ### plot heatmap ###
375
+ data.append(
376
+ go.Heatmap(
377
+ z=self.distance_matrix[order, :][:, order],
378
+ colorscale='Blues',
379
+ reversescale=True,
380
+ hoverinfo='x+y+z'
381
+ )
382
+ )
383
+
384
+ fig = go.Figure(data=data)
385
+
386
+ ### edit layout ###
387
+ if label is not None:
388
+ fig.update_layout(
389
+ {
390
+ 'xaxis': {
391
+ 'tickmode': 'array',
392
+ 'tickvals': np.arange(len(label)),
393
+ 'ticktext': label[order]
394
+ },
395
+ 'yaxis': {
396
+ 'tickmode': 'array',
397
+ 'tickvals': np.arange(len(label)),
398
+ 'ticktext': label[order],
399
+ 'autorange': 'reversed'
400
+ }
401
+ }
402
+ )
403
+
404
+ fig.update_layout(
405
+ {
406
+ 'width': 800,
407
+ 'height': 800,
408
+ 'showlegend': False,
409
+ 'hovermode': 'closest',
410
+ 'xaxis': {
411
+ 'domain': [.15, 1],
412
+ 'rangemode': 'tozero',
413
+ 'mirror': False,
414
+ 'showgrid': False,
415
+ 'showline': False,
416
+ 'zeroline': False,
417
+ 'showticklabels': False,
418
+ 'ticks': ""
419
+ },
420
+ 'xaxis2': {
421
+ 'domain': [0, .15],
422
+ 'rangemode': 'tozero',
423
+ 'mirror': False,
424
+ 'showgrid': False,
425
+ 'showline': False,
426
+ 'zeroline': False,
427
+ 'showticklabels': False,
428
+ 'ticks': ""
429
+ },
430
+ 'yaxis': {
431
+ 'domain': [0, .85],
432
+ 'rangemode': 'tozero',
433
+ 'mirror': False,
434
+ 'showgrid': False,
435
+ 'showline': False,
436
+ 'zeroline': False,
437
+ 'showticklabels': False,
438
+ 'ticks': ""
439
+ },
440
+ 'yaxis2': {
441
+ 'domain':[.825, .975],
442
+ 'rangemode': 'tozero',
443
+ 'mirror': False,
444
+ 'showgrid': False,
445
+ 'showline': False,
446
+ 'zeroline': False,
447
+ 'showticklabels': False,
448
+ 'ticks': ""
449
+ }
450
+ }
451
+ )
452
+
453
+ fig.show()
454
+
455
+ def elbow_plot(self):
456
+ x = np.arange(self.k_max) + 1
457
+
458
+ y = np.asarray([sum(self.linkage_stats[i]['tss'] / len(self.linkage_stats[i]['indices']) for i in self._get_cut_nodes(-1, k)) for k in range(1, self.k_max + 1)])
459
+ y = (y[0] - y) / y[0]
460
+
461
+ fig = go.Figure(data=[go.Scatter(x=x, y=y, mode='lines')])
462
+ fig.show()
463
+
464
+ def silhouette_score_plot(self):
465
+ x = np.arange(2, self.k_max + 1)
466
+ y = [silhouette_score(self.distance_matrix, self.get_cluster_label(k), metric='precomputed') for k in x]
467
+
468
+ fig = go.Figure(data=go.Scatter(x=x, y=y, mode='lines'))
469
+ fig.show()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: csa3d
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: Extract and analyze enzyme catalytic sites in 3D from the Mechanism and Catalytic Site Atlas (M-CSA)
5
5
  Author-email: Ioannis Riziotis <riziotis@ebi.ac.uk>
6
6
  License: Apache-2.0
@@ -30,9 +30,10 @@ Requires-Dist: scipy>=1.7.0
30
30
  Requires-Dist: scikit-learn>=0.24.0
31
31
  Requires-Dist: rdkit>=2021.09.0
32
32
  Requires-Dist: natsort>=8.0.0
33
- Requires-Dist: dendrogram-cut>=0.1.0
34
- Requires-Dist: parity-core>=0.5.0
35
33
  Requires-Dist: rmsd>=1.4.0
34
+ Provides-Extra: clustering
35
+ Requires-Dist: matplotlib>=3.5; extra == "clustering"
36
+ Requires-Dist: plotly>=5.0; extra == "clustering"
36
37
  Provides-Extra: dev
37
38
  Requires-Dist: pytest>=7.0; extra == "dev"
38
39
  Requires-Dist: pytest-cov>=4.0; extra == "dev"
@@ -6,6 +6,8 @@ csa3d.egg-info/SOURCES.txt
6
6
  csa3d.egg-info/dependency_links.txt
7
7
  csa3d.egg-info/requires.txt
8
8
  csa3d.egg-info/top_level.txt
9
+ csa3d/_vendor/__init__.py
10
+ csa3d/_vendor/dendrogram_cut.py
9
11
  csa3d/config/__init__.py
10
12
  csa3d/config/loader.py
11
13
  csa3d/config/settings.py
@@ -5,10 +5,12 @@ scipy>=1.7.0
5
5
  scikit-learn>=0.24.0
6
6
  rdkit>=2021.09.0
7
7
  natsort>=8.0.0
8
- dendrogram-cut>=0.1.0
9
- parity-core>=0.5.0
10
8
  rmsd>=1.4.0
11
9
 
10
+ [clustering]
11
+ matplotlib>=3.5
12
+ plotly>=5.0
13
+
12
14
  [dev]
13
15
  pytest>=7.0
14
16
  pytest-cov>=4.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "csa3d"
7
- version = "0.2.0"
7
+ version = "0.2.1"
8
8
  description = "Extract and analyze enzyme catalytic sites in 3D from the Mechanism and Catalytic Site Atlas (M-CSA)"
9
9
  readme = "README.md"
10
10
  license = {text = "Apache-2.0"}
@@ -34,12 +34,14 @@ dependencies = [
34
34
  "scikit-learn>=0.24.0",
35
35
  "rdkit>=2021.09.0",
36
36
  "natsort>=8.0.0",
37
- "dendrogram-cut>=0.1.0",
38
- "parity-core>=0.5.0",
39
37
  "rmsd>=1.4.0",
40
38
  ]
41
39
 
42
40
  [project.optional-dependencies]
41
+ clustering = [
42
+ "matplotlib>=3.5",
43
+ "plotly>=5.0",
44
+ ]
43
45
  dev = [
44
46
  "pytest>=7.0",
45
47
  "pytest-cov>=4.0",
@@ -68,7 +70,7 @@ Repository = "https://github.com/your-username/csa3d"
68
70
  "Issue Tracker" = "https://github.com/your-username/csa3d/issues"
69
71
 
70
72
  [tool.setuptools]
71
- packages = ["csa3d", "csa3d.config"]
73
+ packages = ["csa3d", "csa3d.config", "csa3d._vendor"]
72
74
 
73
75
  [tool.black]
74
76
  line-length = 100
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes