PGAP2 2.3__tar.gz → 2.4__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.
Files changed (90) hide show
  1. {pgap2-2.3 → pgap2-2.4/PGAP2.egg-info}/PKG-INFO +1 -1
  2. {pgap2-2.3/PGAP2.egg-info → pgap2-2.4}/PKG-INFO +1 -1
  3. {pgap2-2.3 → pgap2-2.4}/pgap2/cli/neighborhood.py +7 -1
  4. {pgap2-2.3 → pgap2-2.4}/pgap2/lib/synteny.py +3 -2
  5. {pgap2-2.3 → pgap2-2.4}/pgap2/main.py +1 -1
  6. {pgap2-2.3 → pgap2-2.4}/pgap2/postprocess/profile.py +1 -1
  7. {pgap2-2.3 → pgap2-2.4}/pgap2/postprocess/stat.py +135 -19
  8. {pgap2-2.3 → pgap2-2.4}/pgap2/utils/sqlite_builder.py +3 -5
  9. {pgap2-2.3 → pgap2-2.4}/pyproject.toml +1 -1
  10. {pgap2-2.3 → pgap2-2.4}/tests/test_post.py +48 -0
  11. {pgap2-2.3 → pgap2-2.4}/LICENSE +0 -0
  12. {pgap2-2.3 → pgap2-2.4}/MANIFEST.in +0 -0
  13. {pgap2-2.3 → pgap2-2.4}/PGAP2.egg-info/SOURCES.txt +0 -0
  14. {pgap2-2.3 → pgap2-2.4}/PGAP2.egg-info/dependency_links.txt +0 -0
  15. {pgap2-2.3 → pgap2-2.4}/PGAP2.egg-info/entry_points.txt +0 -0
  16. {pgap2-2.3 → pgap2-2.4}/PGAP2.egg-info/requires.txt +0 -0
  17. {pgap2-2.3 → pgap2-2.4}/PGAP2.egg-info/top_level.txt +0 -0
  18. {pgap2-2.3 → pgap2-2.4}/README.md +0 -0
  19. {pgap2-2.3 → pgap2-2.4}/pgap2/__init__.py +0 -0
  20. {pgap2-2.3 → pgap2-2.4}/pgap2/cli/__init__.py +0 -0
  21. {pgap2-2.3 → pgap2-2.4}/pgap2/cli/partition.py +0 -0
  22. {pgap2-2.3 → pgap2-2.4}/pgap2/cli/partition_add.py +0 -0
  23. {pgap2-2.3 → pgap2-2.4}/pgap2/cli/partition_merge.py +0 -0
  24. {pgap2-2.3 → pgap2-2.4}/pgap2/cli/partition_minus.py +0 -0
  25. {pgap2-2.3 → pgap2-2.4}/pgap2/cli/postprocess.py +0 -0
  26. {pgap2-2.3 → pgap2-2.4}/pgap2/cli/preprocess.py +0 -0
  27. {pgap2-2.3 → pgap2-2.4}/pgap2/cli/sqlite_cmd.py +0 -0
  28. {pgap2-2.3 → pgap2-2.4}/pgap2/dependencies/draw_post_profile.r +0 -0
  29. {pgap2-2.3 → pgap2-2.4}/pgap2/dependencies/draw_post_stat.r +0 -0
  30. {pgap2-2.3 → pgap2-2.4}/pgap2/dependencies/draw_prep.r +0 -0
  31. {pgap2-2.3 → pgap2-2.4}/pgap2/dependencies/maskrc-svg.py +0 -0
  32. {pgap2-2.3 → pgap2-2.4}/pgap2/dependencies/run_fastbaps +0 -0
  33. {pgap2-2.3 → pgap2-2.4}/pgap2/lib/__init__.py +0 -0
  34. {pgap2-2.3 → pgap2-2.4}/pgap2/lib/basic.py +0 -0
  35. {pgap2-2.3 → pgap2-2.4}/pgap2/lib/panclust.py +0 -0
  36. {pgap2-2.3 → pgap2-2.4}/pgap2/lib/pangenome.py +0 -0
  37. {pgap2-2.3 → pgap2-2.4}/pgap2/lib/pangp.py +0 -0
  38. {pgap2-2.3 → pgap2-2.4}/pgap2/lib/phylogeny.py +0 -0
  39. {pgap2-2.3 → pgap2-2.4}/pgap2/lib/pklcheck.py +0 -0
  40. {pgap2-2.3 → pgap2-2.4}/pgap2/lib/species.py +0 -0
  41. {pgap2-2.3 → pgap2-2.4}/pgap2/lib/strain.py +0 -0
  42. {pgap2-2.3 → pgap2-2.4}/pgap2/lib/tree.py +0 -0
  43. {pgap2-2.3 → pgap2-2.4}/pgap2/models/__init__.py +0 -0
  44. {pgap2-2.3 → pgap2-2.4}/pgap2/models/baps.py +0 -0
  45. {pgap2-2.3 → pgap2-2.4}/pgap2/models/base.py +0 -0
  46. {pgap2-2.3 → pgap2-2.4}/pgap2/models/gene_annotation.py +0 -0
  47. {pgap2-2.3 → pgap2-2.4}/pgap2/models/gene_cluster.py +0 -0
  48. {pgap2-2.3 → pgap2-2.4}/pgap2/models/gene_neighborhood.py +0 -0
  49. {pgap2-2.3 → pgap2-2.4}/pgap2/models/pav.py +0 -0
  50. {pgap2-2.3 → pgap2-2.4}/pgap2/models/phylogeny.py +0 -0
  51. {pgap2-2.3 → pgap2-2.4}/pgap2/models/preprocess.py +0 -0
  52. {pgap2-2.3 → pgap2-2.4}/pgap2/models/project.py +0 -0
  53. {pgap2-2.3 → pgap2-2.4}/pgap2/models/project_file.py +0 -0
  54. {pgap2-2.3 → pgap2-2.4}/pgap2/models/statistics.py +0 -0
  55. {pgap2-2.3 → pgap2-2.4}/pgap2/models/strain.py +0 -0
  56. {pgap2-2.3 → pgap2-2.4}/pgap2/models/tajimas_d.py +0 -0
  57. {pgap2-2.3 → pgap2-2.4}/pgap2/postprocess/__init__.py +0 -0
  58. {pgap2-2.3 → pgap2-2.4}/pgap2/postprocess/baps.py +0 -0
  59. {pgap2-2.3 → pgap2-2.4}/pgap2/postprocess/graph.py +0 -0
  60. {pgap2-2.3 → pgap2-2.4}/pgap2/postprocess/neighborhood.py +0 -0
  61. {pgap2-2.3 → pgap2-2.4}/pgap2/postprocess/reference_layout.py +0 -0
  62. {pgap2-2.3 → pgap2-2.4}/pgap2/postprocess/singletree.py +0 -0
  63. {pgap2-2.3 → pgap2-2.4}/pgap2/postprocess/tajimas_d.py +0 -0
  64. {pgap2-2.3 → pgap2-2.4}/pgap2/toy_input/287_91.fa +0 -0
  65. {pgap2-2.3 → pgap2-2.4}/pgap2/toy_input/287_91.gff +0 -0
  66. {pgap2-2.3 → pgap2-2.4}/pgap2/toy_input/AKU_12601.gff +0 -0
  67. {pgap2-2.3 → pgap2-2.4}/pgap2/toy_input/Readme_input.md +0 -0
  68. {pgap2-2.3 → pgap2-2.4}/pgap2/toy_input/SC_B67.gbk +0 -0
  69. {pgap2-2.3 → pgap2-2.4}/pgap2/utils/__init__.py +0 -0
  70. {pgap2-2.3 → pgap2-2.4}/pgap2/utils/alignment.py +0 -0
  71. {pgap2-2.3 → pgap2-2.4}/pgap2/utils/arrangement_detector.py +0 -0
  72. {pgap2-2.3 → pgap2-2.4}/pgap2/utils/data_loader.py +0 -0
  73. {pgap2-2.3 → pgap2-2.4}/pgap2/utils/draw.py +0 -0
  74. {pgap2-2.3 → pgap2-2.4}/pgap2/utils/gene_retriever.py +0 -0
  75. {pgap2-2.3 → pgap2-2.4}/pgap2/utils/generate_tree.py +0 -0
  76. {pgap2-2.3 → pgap2-2.4}/pgap2/utils/graph_utils.py +0 -0
  77. {pgap2-2.3 → pgap2-2.4}/pgap2/utils/neighborhood.py +0 -0
  78. {pgap2-2.3 → pgap2-2.4}/pgap2/utils/partition.py +0 -0
  79. {pgap2-2.3 → pgap2-2.4}/pgap2/utils/partition_add.py +0 -0
  80. {pgap2-2.3 → pgap2-2.4}/pgap2/utils/partition_merge.py +0 -0
  81. {pgap2-2.3 → pgap2-2.4}/pgap2/utils/partition_minus.py +0 -0
  82. {pgap2-2.3 → pgap2-2.4}/pgap2/utils/postprocess.py +0 -0
  83. {pgap2-2.3 → pgap2-2.4}/pgap2/utils/preprocess.py +0 -0
  84. {pgap2-2.3 → pgap2-2.4}/pgap2/utils/supply.py +0 -0
  85. {pgap2-2.3 → pgap2-2.4}/pgap2/utils/tools.py +0 -0
  86. {pgap2-2.3 → pgap2-2.4}/setup.cfg +0 -0
  87. {pgap2-2.3 → pgap2-2.4}/tests/test_main.py +0 -0
  88. {pgap2-2.3 → pgap2-2.4}/tests/test_pangenome.py +0 -0
  89. {pgap2-2.3 → pgap2-2.4}/tests/test_prep.py +0 -0
  90. {pgap2-2.3 → pgap2-2.4}/tests/test_synteny.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PGAP2
3
- Version: 2.3
3
+ Version: 2.4
4
4
  Summary: PGAP2: a comprehensive pan-genome analysis pipeline for prokaryotic genomes
5
5
  Author-email: bucongfan <bucf@big.ac.cn>
6
6
  Maintainer-email: bucongfan <bucf@big.ac.cn>
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PGAP2
3
- Version: 2.3
3
+ Version: 2.4
4
4
  Summary: PGAP2: a comprehensive pan-genome analysis pipeline for prokaryotic genomes
5
5
  Author-email: bucongfan <bucf@big.ac.cn>
6
6
  Maintainer-email: bucongfan <bucf@big.ac.cn>
@@ -4,6 +4,12 @@ CLI definitions for the 'neigh' (conserved gene neighborhood) subcommand.
4
4
  This subcommand runs the full partition pipeline (identical to 'main')
5
5
  followed by a conserved gene neighborhood discovery step inspired by
6
6
  spacedust's agglomerative clustering algorithm.
7
+
8
+ spacedust:
9
+ https://github.com/soedinglab/spacedust
10
+ Zhang, R., Mirdita, M. & Söding, J. De novo discovery of conserved gene clusters in microbial genomes with Spacedust.
11
+ Nat Methods 22, 2065–2073 (2025). https://doi.org/10.1038/s41592-025-02816-x
12
+
7
13
  """
8
14
 
9
15
  import os
@@ -79,7 +85,7 @@ def neighborhood_cmd(subparser: _SubParsersAction):
79
85
  'neigh',
80
86
  help='Conserved gene neighborhood (synteny block) analysis. '
81
87
  'Runs the full partition pipeline then discovers conserved '
82
- 'gene neighborhoods across genomes (spacedust-style).',
88
+ 'gene neighborhoods across genomes.',
83
89
  formatter_class=argparse.ArgumentDefaultsHelpFormatter)
84
90
 
85
91
  # ---- Partition parameters (same as 'main') ----
@@ -17,8 +17,9 @@ The algorithm works in four steps:
17
17
  gene neighborhoods.
18
18
 
19
19
  Reference:
20
- Zhang, R., Mirdita, M., & Söding, J. (2024). De novo discovery of conserved
21
- gene clusters in microbial genomes with Spacedust. bioRxiv.
20
+ Zhang, R., Mirdita, M. & Söding, J. De novo discovery of conserved gene clusters in microbial genomes with Spacedust.
21
+ Nat Methods 22, 2065–2073 (2025).
22
+ https://doi.org/10.1038/s41592-025-02816-x
22
23
  """
23
24
 
24
25
  import math
@@ -23,7 +23,7 @@ def main():
23
23
  parser.add_argument(
24
24
  "-V", "--version",
25
25
  action="version",
26
- version=f"%(prog)s {__version__} (author: {__author__})"
26
+ version=f"%(prog)s current version: {__version__}"
27
27
  )
28
28
  subparser: _SubParsersAction = parser.add_subparsers()
29
29
 
@@ -55,7 +55,7 @@ def main(pav_file: str, outdir: str, nodraw: bool, threads: int = 1, disable: bo
55
55
  group_freq, pan_para_stat = get_pan_group(pav, outdir)
56
56
 
57
57
  logger.info("Starting rarefaction curve fitting.")
58
- fit_rerefaction(pan_profile, outdir)
58
+ fit_rerefaction(pan_profile, outdir, new_clusters)
59
59
 
60
60
  if nodraw:
61
61
  logger.info('Drawing is disabled')
@@ -55,7 +55,12 @@ def core_gene_model(x, C0, a, C_inf):
55
55
  def pan_gene_model(x, k, gamma):
56
56
  return k * x**gamma
57
57
 
58
- #make sure that only finite values are used in the fitting process.
58
+
59
+ def new_gene_model(x, k, alpha):
60
+ return k * x**(-alpha)
61
+
62
+
63
+ # Make sure that only finite values are used in the fitting process.
59
64
  def _drop_invalid_fit_points(x_data, y_data):
60
65
  x_data = np.asarray(x_data, dtype=float)
61
66
  y_data = np.asarray(y_data, dtype=float)
@@ -90,6 +95,93 @@ def _fit_pan_gene_loglog(x_data, pan_gene_means):
90
95
  return np.asarray([k, gamma], dtype=float)
91
96
 
92
97
 
98
+ def _prepare_new_gene_fit_data(new_clusters):
99
+ if new_clusters is None or len(new_clusters) != 2:
100
+ raise ValueError("New gene cluster data are required for pan-genome state fitting.")
101
+
102
+ x_data = np.asarray(new_clusters[0], dtype=float)
103
+ sample_groups = new_clusters[1]
104
+ if len(x_data) != len(sample_groups):
105
+ raise ValueError("New gene cluster strain counts and samples have different lengths.")
106
+
107
+ means = []
108
+ for samples in sample_groups:
109
+ values = np.asarray(
110
+ [value for value in samples if value != 'NA'], dtype=float)
111
+ values = values[np.isfinite(values)]
112
+ means.append(np.mean(values) if len(values) else np.nan)
113
+
114
+ x_data, means = _drop_invalid_fit_points(x_data, means)
115
+ valid_points = (x_data > 0) & (means >= 0)
116
+ x_data = x_data[valid_points]
117
+ means = means[valid_points]
118
+ if len(np.unique(x_data)) < 3:
119
+ raise ValueError(
120
+ "At least three distinct strain counts are required for pan-genome state fitting.")
121
+ return x_data, means
122
+
123
+
124
+ def _fit_new_gene_decay(new_clusters):
125
+ x_data, new_gene_means = _prepare_new_gene_fit_data(new_clusters)
126
+ if np.all(new_gene_means == 0):
127
+ return np.asarray([0.0, np.inf]), None
128
+
129
+ alpha_initial = 0.5
130
+ k_initial = max(
131
+ float(new_gene_means[0]) * float(x_data[0])**alpha_initial,
132
+ np.finfo(float).eps,
133
+ )
134
+ try:
135
+ return curve_fit(
136
+ new_gene_model,
137
+ x_data,
138
+ new_gene_means,
139
+ p0=[k_initial, alpha_initial],
140
+ bounds=([0, 0], [np.inf, np.inf]),
141
+ maxfev=10000,
142
+ )
143
+ except (RuntimeError, ValueError):
144
+ positive_points = new_gene_means > 0
145
+ if np.count_nonzero(positive_points) < 3:
146
+ raise RuntimeError(
147
+ "At least three positive points are required for log-log new gene fitting.")
148
+
149
+ coefficients, log_covariance = np.polyfit(
150
+ np.log(x_data[positive_points]),
151
+ np.log(new_gene_means[positive_points]),
152
+ 1,
153
+ cov=True,
154
+ )
155
+ slope, log_k = coefficients
156
+ popt = np.asarray([np.exp(log_k), -slope], dtype=float)
157
+ covariance = np.zeros((2, 2), dtype=float)
158
+ covariance[1, 1] = log_covariance[0, 0]
159
+ return popt, covariance
160
+
161
+
162
+ def _get_alpha_confidence_interval(alpha, covariance):
163
+ if covariance is None or not np.isfinite(alpha):
164
+ return None
165
+ alpha_variance = covariance[1, 1]
166
+ if not np.isfinite(alpha_variance) or alpha_variance < 0:
167
+ return None
168
+ margin = 1.96 * np.sqrt(alpha_variance)
169
+ return alpha - margin, alpha + margin
170
+
171
+
172
+ def _classify_pan_genome(alpha, alpha_interval):
173
+ if np.isposinf(alpha):
174
+ return "closed"
175
+ if alpha_interval is None:
176
+ return "uncertain"
177
+ lower_alpha, upper_alpha = alpha_interval
178
+ if lower_alpha > 1:
179
+ return "closed"
180
+ if upper_alpha <= 1:
181
+ return "open"
182
+ return "uncertain"
183
+
184
+
93
185
  def stat_pan_core(sampling_list, pav, i_strain, strain_num):
94
186
  core_list = []
95
187
  pan_list = []
@@ -131,30 +223,33 @@ def calculate_rarefaction(i_strain, pangp: PanGP):
131
223
  return {'strain_num': i_strain, 'profile': record, 'new_clusters': new_clusters, }
132
224
 
133
225
 
134
- def log_pan_genome_state(popt_pan, ofh):
135
- k, gamma = popt_pan
136
- if gamma < 1:
137
- genome_status = "open"
226
+ def log_pan_genome_state(alpha, alpha_interval, genome_status, ofh):
227
+ if genome_status == "open":
138
228
  status_description = (
139
- "The pan-genome is open, meaning that the discovery of new genes will continue as more genomes are added."
229
+ "The pan-genome is open because the 95% confidence interval for alpha is at or below 1."
230
+ )
231
+ elif genome_status == "closed":
232
+ status_description = (
233
+ "The pan-genome is closed because no new clusters were observed or the 95% confidence interval for alpha is above 1."
140
234
  )
141
235
  else:
142
- genome_status = "closed"
143
236
  status_description = (
144
- "The pan-genome is closed, meaning that the discovery of new genes will saturate as more genomes are added."
237
+ "The pan-genome state is uncertain because the alpha confidence interval crosses 1."
145
238
  )
146
239
  logger.info(
147
- f"The pan-genome is {genome_status}. {status_description}")
148
- ofh.write(
149
- f"The pan-genome is {genome_status}. {status_description}\n")
240
+ status_description)
241
+ ofh.write(f"{status_description}\n")
150
242
 
151
- logger.info(
152
- f"The coefficient k is approximately {k:.2f}, indicating the initial addition rate of new gene clusters per genome.\nLarger values of k suggest a higher initial rate of gene discovery. ")
153
- ofh.write(
154
- f"The coefficient k is approximately {k:.2f}, indicating the initial addition rate of new gene clusters per genome.\nLarger values of k suggest a higher initial rate of gene discovery. ")
243
+ if np.isfinite(alpha):
244
+ ofh.write(f"The new-gene decay exponent alpha is {alpha:.3f}.\n")
245
+ else:
246
+ ofh.write("The new-gene decay exponent alpha is infinite.\n")
247
+ if alpha_interval is not None:
248
+ ofh.write(
249
+ f"The 95% confidence interval for alpha is [{alpha_interval[0]:.3f}, {alpha_interval[1]:.3f}].\n")
155
250
 
156
251
 
157
- def fit_rerefaction(pan_rarefaction, outdir):
252
+ def fit_rerefaction(pan_rarefaction, outdir, new_clusters=None):
158
253
 
159
254
  x_data = np.asarray(pan_rarefaction[0], dtype=float)
160
255
  core_gene_samples = pan_rarefaction[1]
@@ -232,11 +327,32 @@ def fit_rerefaction(pan_rarefaction, outdir):
232
327
 
233
328
  if popt_pan is None:
234
329
  ofh.write('Pan Gene Fit: NA\n')
235
- ofh.write('Pan-genome state: NA\n')
236
330
  else:
237
331
  ofh.write(
238
332
  f'Pan Gene Fit: y={popt_pan[0]:.3f}*x^{popt_pan[1]:.3f} \n')
239
- log_pan_genome_state(popt_pan, ofh)
333
+
334
+ logger.info("Fitting new gene decay data for pan-genome state classification.")
335
+ try:
336
+ popt_new, covariance_new = _fit_new_gene_decay(new_clusters)
337
+ k_new, alpha = popt_new
338
+ alpha_interval = _get_alpha_confidence_interval(
339
+ alpha, covariance_new)
340
+ genome_status = _classify_pan_genome(alpha, alpha_interval)
341
+ logger.info(f"Optimal parameters for new genes: {popt_new}")
342
+ ofh.write(f'Optimal parameters for new genes: {popt_new}\n')
343
+ if np.isfinite(alpha):
344
+ ofh.write(
345
+ f'New Gene Fit: y={k_new:.3f}*x^(-{alpha:.3f})\n')
346
+ else:
347
+ ofh.write('New Gene Fit: y=0\n')
348
+ log_pan_genome_state(
349
+ alpha, alpha_interval, genome_status, ofh)
350
+ except (RuntimeError, ValueError) as err:
351
+ logger.warning(
352
+ f"Pan-genome state fitting could not be completed: {err}")
353
+ ofh.write('Optimal parameters for new genes: NA\n')
354
+ ofh.write('New Gene Fit: NA\n')
355
+ ofh.write('Pan-genome state: NA\n')
240
356
 
241
357
  # plt.figure(figsize=(8, 6))
242
358
 
@@ -443,7 +559,7 @@ def main(indir: str, outdir: str, nodraw: bool, threads: int, disable: bool = Fa
443
559
  pan_profile, new_clusters = get_rarefaction(
444
560
  basic.pav, outdir, disable=disable, threads=threads, S=S, N=N, R=R, K=K, method=method)
445
561
  logger.info("Starting rarefaction curve fitting.")
446
- fit_rerefaction(pan_profile, outdir)
562
+ fit_rerefaction(pan_profile, outdir, new_clusters)
447
563
 
448
564
  if nodraw:
449
565
  logger.info('Drawing is disabled')
@@ -714,11 +714,9 @@ def _import_post_results(db: Session, project_id: int, data_path: Path):
714
714
  if curve_file.exists():
715
715
  content = curve_file.read_text().strip()
716
716
  if content:
717
- openness = None
718
- if "open" in content.lower():
719
- openness = "open"
720
- elif "closed" in content.lower():
721
- openness = "closed"
717
+ state_match = re.search(
718
+ r"The pan-genome is (open|closed)\b", content, re.IGNORECASE)
719
+ openness = state_match.group(1).lower() if state_match else None
722
720
  db.add(CurveFit(
723
721
  project_id=project_id,
724
722
  model_name="heaps",
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "PGAP2"
7
- version = "2.3"
7
+ version = "2.4"
8
8
  dynamic = ["readme"]
9
9
  keywords = ["pan-genome", "prokaryotic", "genomes", "pipeline","cluster"]
10
10
 
@@ -93,3 +93,51 @@ def test_fit_rarefaction_uses_pan_loglog_fallback(tmp_path, monkeypatch):
93
93
  assert "Optimal parameters for pan genes:" in content
94
94
  assert "log-log fallback" in content
95
95
  assert "Pan Gene Fit: y=" in content
96
+
97
+
98
+ @pytest.mark.parametrize(
99
+ ("alpha", "expected_state"),
100
+ [
101
+ (0.5, "open"),
102
+ (1.5, "closed"),
103
+ ],
104
+ )
105
+ def test_fit_rarefaction_classifies_state_from_new_genes(
106
+ tmp_path, alpha, expected_state):
107
+ from pgap2.postprocess import stat
108
+
109
+ x_data = np.asarray([1, 2, 4, 8, 16, 32], dtype=float)
110
+ pan_profile = [
111
+ x_data.tolist(),
112
+ [[3000 - 20 * i] for i in range(len(x_data))],
113
+ [[4000 + 300 * np.sqrt(x)] for x in x_data],
114
+ ]
115
+ new_x_data = x_data[1:]
116
+ new_gene_means = 1000 * new_x_data**(-alpha)
117
+ new_clusters = [
118
+ new_x_data.tolist(),
119
+ [[value] for value in new_gene_means],
120
+ ]
121
+
122
+ stat.fit_rerefaction(pan_profile, tmp_path, new_clusters)
123
+
124
+ content = (tmp_path / "postprocess.curve_fit.txt").read_text()
125
+ assert "New Gene Fit: y=" in content
126
+ assert f"The pan-genome is {expected_state}" in content
127
+
128
+
129
+ def test_fit_rarefaction_reports_na_with_too_few_new_gene_points(tmp_path):
130
+ from pgap2.postprocess import stat
131
+
132
+ pan_profile = [
133
+ [1, 2, 3, 4],
134
+ [[3000], [2500], [2300], [2200]],
135
+ [[4000], [4500], [4800], [5100]],
136
+ ]
137
+ new_clusters = [[2, 3], [[500], [200]]]
138
+
139
+ stat.fit_rerefaction(pan_profile, tmp_path, new_clusters)
140
+
141
+ content = (tmp_path / "postprocess.curve_fit.txt").read_text()
142
+ assert "Optimal parameters for new genes: NA" in content
143
+ assert "Pan-genome state: NA" in content
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes