QCut 2.3.0__tar.gz → 2.4.0__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 (148) hide show
  1. {qcut-2.3.0 → qcut-2.4.0}/CHANGELOG.rst +21 -0
  2. {qcut-2.3.0 → qcut-2.4.0}/PKG-INFO +37 -25
  3. {qcut-2.3.0 → qcut-2.4.0}/QCut/__init__.py +5 -0
  4. {qcut-2.3.0 → qcut-2.4.0}/QCut/execution/circuit_knitting.py +225 -147
  5. qcut-2.4.0/QCut/execution/job.py +507 -0
  6. {qcut-2.3.0 → qcut-2.4.0}/QCut/execution/parallel.py +1 -1
  7. {qcut-2.3.0 → qcut-2.4.0}/QCut/execution/postprocess.py +2 -2
  8. {qcut-2.3.0 → qcut-2.4.0}/QCut/execution/probabilities.py +3 -3
  9. {qcut-2.3.0 → qcut-2.4.0}/QCut/options.py +2 -2
  10. qcut-2.4.0/QCut/utils/deprecation.py +40 -0
  11. {qcut-2.3.0 → qcut-2.4.0}/QCut.egg-info/PKG-INFO +37 -25
  12. {qcut-2.3.0 → qcut-2.4.0}/QCut.egg-info/SOURCES.txt +3 -0
  13. {qcut-2.3.0 → qcut-2.4.0}/QCut.egg-info/scm_file_list.json +3 -0
  14. qcut-2.4.0/QCut.egg-info/scm_version.json +8 -0
  15. {qcut-2.3.0 → qcut-2.4.0}/README.md +36 -24
  16. {qcut-2.3.0 → qcut-2.4.0}/docs/Usage.rst +24 -6
  17. {qcut-2.3.0 → qcut-2.4.0}/docs/examples/ParallelBackends.rst +3 -3
  18. {qcut-2.3.0 → qcut-2.4.0}/docs/notebooks/QCutBasicUsage.ipynb +3 -3
  19. {qcut-2.3.0 → qcut-2.4.0}/docs/notebooks/QCutOptionsExample.ipynb +1 -1
  20. {qcut-2.3.0 → qcut-2.4.0}/docs/notebooks/QCutProbabilities.ipynb +1 -1
  21. {qcut-2.3.0 → qcut-2.4.0}/docs/notebooks/QCutQAOAExample.ipynb +1 -1
  22. {qcut-2.3.0 → qcut-2.4.0}/pyproject.toml +3 -0
  23. {qcut-2.3.0 → qcut-2.4.0}/tests/test_cut_circuit.py +6 -6
  24. {qcut-2.3.0 → qcut-2.4.0}/tests/test_deduplication.py +2 -1
  25. qcut-2.4.0/tests/test_job.py +262 -0
  26. {qcut-2.3.0 → qcut-2.4.0}/tox.ini +1 -0
  27. qcut-2.3.0/QCut.egg-info/scm_version.json +0 -8
  28. {qcut-2.3.0 → qcut-2.4.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  29. {qcut-2.3.0 → qcut-2.4.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  30. {qcut-2.3.0 → qcut-2.4.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  31. {qcut-2.3.0 → qcut-2.4.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  32. {qcut-2.3.0 → qcut-2.4.0}/.github/dependabot.yml +0 -0
  33. {qcut-2.3.0 → qcut-2.4.0}/.github/workflows/ci.yml +0 -0
  34. {qcut-2.3.0 → qcut-2.4.0}/.github/workflows/publish_docs.yml +0 -0
  35. {qcut-2.3.0 → qcut-2.4.0}/.github/workflows/publish_to_pypi.yml +0 -0
  36. {qcut-2.3.0 → qcut-2.4.0}/.github/workflows/tag_and_release.yml +0 -0
  37. {qcut-2.3.0 → qcut-2.4.0}/.gitignore +0 -0
  38. {qcut-2.3.0 → qcut-2.4.0}/CONTRIBUTING.md +0 -0
  39. {qcut-2.3.0 → qcut-2.4.0}/LICENSE +0 -0
  40. {qcut-2.3.0 → qcut-2.4.0}/QCut/QCutFind/__init__.py +0 -0
  41. {qcut-2.3.0 → qcut-2.4.0}/QCut/QCutFind/combine_subcircuits.py +0 -0
  42. {qcut-2.3.0 → qcut-2.4.0}/QCut/QCutFind/cut_finding.py +0 -0
  43. {qcut-2.3.0 → qcut-2.4.0}/QCut/QCutFind/graph_circuit_utils.py +0 -0
  44. {qcut-2.3.0 → qcut-2.4.0}/QCut/QCutFind/metis.py +0 -0
  45. {qcut-2.3.0 → qcut-2.4.0}/QCut/QCutFind/refine.py +0 -0
  46. {qcut-2.3.0 → qcut-2.4.0}/QCut/cutcircuit.py +0 -0
  47. {qcut-2.3.0 → qcut-2.4.0}/QCut/cutlocation.py +0 -0
  48. {qcut-2.3.0 → qcut-2.4.0}/QCut/cutting/__init__.py +0 -0
  49. {qcut-2.3.0 → qcut-2.4.0}/QCut/cutting/circuit_preparation.py +0 -0
  50. {qcut-2.3.0 → qcut-2.4.0}/QCut/cutting/consolidate.py +0 -0
  51. {qcut-2.3.0 → qcut-2.4.0}/QCut/errors/__init__.py +0 -0
  52. {qcut-2.3.0 → qcut-2.4.0}/QCut/errors/qcuterror.py +0 -0
  53. {qcut-2.3.0 → qcut-2.4.0}/QCut/execution/__init__.py +0 -0
  54. {qcut-2.3.0 → qcut-2.4.0}/QCut/execution/backend_utility.py +0 -0
  55. {qcut-2.3.0 → qcut-2.4.0}/QCut/execution/basis_transform.py +0 -0
  56. {qcut-2.3.0 → qcut-2.4.0}/QCut/execution/distribution.py +0 -0
  57. {qcut-2.3.0 → qcut-2.4.0}/QCut/execution/move_routing.py +0 -0
  58. {qcut-2.3.0 → qcut-2.4.0}/QCut/execution/observables.py +0 -0
  59. {qcut-2.3.0 → qcut-2.4.0}/QCut/execution/qcutresult.py +0 -0
  60. {qcut-2.3.0 → qcut-2.4.0}/QCut/qpd/__init__.py +0 -0
  61. {qcut-2.3.0 → qcut-2.4.0}/QCut/qpd/bundle.py +0 -0
  62. {qcut-2.3.0 → qcut-2.4.0}/QCut/qpd/qpd.py +0 -0
  63. {qcut-2.3.0 → qcut-2.4.0}/QCut/qpd/qpd_analytic.py +0 -0
  64. {qcut-2.3.0 → qcut-2.4.0}/QCut/qpd/qpd_gates.py +0 -0
  65. {qcut-2.3.0 → qcut-2.4.0}/QCut/qpd/qpd_generate.py +0 -0
  66. {qcut-2.3.0 → qcut-2.4.0}/QCut/qpd/qpd_joint.py +0 -0
  67. {qcut-2.3.0 → qcut-2.4.0}/QCut/qpd/qpd_locc.py +0 -0
  68. {qcut-2.3.0 → qcut-2.4.0}/QCut/qpd/qpd_operations.py +0 -0
  69. {qcut-2.3.0 → qcut-2.4.0}/QCut/utils/__init__.py +0 -0
  70. {qcut-2.3.0 → qcut-2.4.0}/QCut/utils/circuit_utils.py +0 -0
  71. {qcut-2.3.0 → qcut-2.4.0}/QCut.egg-info/dependency_links.txt +0 -0
  72. {qcut-2.3.0 → qcut-2.4.0}/QCut.egg-info/requires.txt +0 -0
  73. {qcut-2.3.0 → qcut-2.4.0}/QCut.egg-info/top_level.txt +0 -0
  74. {qcut-2.3.0 → qcut-2.4.0}/benchmarks/QCutVsAddon.ipynb +0 -0
  75. {qcut-2.3.0 → qcut-2.4.0}/docs/Acknowledgement.rst +0 -0
  76. {qcut-2.3.0 → qcut-2.4.0}/docs/Changelog.rst +0 -0
  77. {qcut-2.3.0 → qcut-2.4.0}/docs/Examples.rst +0 -0
  78. {qcut-2.3.0 → qcut-2.4.0}/docs/License.rst +0 -0
  79. {qcut-2.3.0 → qcut-2.4.0}/docs/Makefile +0 -0
  80. {qcut-2.3.0 → qcut-2.4.0}/docs/Notebooks.rst +0 -0
  81. {qcut-2.3.0 → qcut-2.4.0}/docs/Options.rst +0 -0
  82. {qcut-2.3.0 → qcut-2.4.0}/docs/QCut.rst +0 -0
  83. {qcut-2.3.0 → qcut-2.4.0}/docs/Theory.rst +0 -0
  84. {qcut-2.3.0 → qcut-2.4.0}/docs/_static/images/circ1.png +0 -0
  85. {qcut-2.3.0 → qcut-2.4.0}/docs/_static/images/circ10.png +0 -0
  86. {qcut-2.3.0 → qcut-2.4.0}/docs/_static/images/circ11.png +0 -0
  87. {qcut-2.3.0 → qcut-2.4.0}/docs/_static/images/circ1_old.png +0 -0
  88. {qcut-2.3.0 → qcut-2.4.0}/docs/_static/images/circ2.png +0 -0
  89. {qcut-2.3.0 → qcut-2.4.0}/docs/_static/images/circ2_old.png +0 -0
  90. {qcut-2.3.0 → qcut-2.4.0}/docs/_static/images/circ3.png +0 -0
  91. {qcut-2.3.0 → qcut-2.4.0}/docs/_static/images/circ3_old.png +0 -0
  92. {qcut-2.3.0 → qcut-2.4.0}/docs/_static/images/circ4.png +0 -0
  93. {qcut-2.3.0 → qcut-2.4.0}/docs/_static/images/circ4_odl.png +0 -0
  94. {qcut-2.3.0 → qcut-2.4.0}/docs/_static/images/circ5.png +0 -0
  95. {qcut-2.3.0 → qcut-2.4.0}/docs/_static/images/circ6.png +0 -0
  96. {qcut-2.3.0 → qcut-2.4.0}/docs/_static/images/circ7.png +0 -0
  97. {qcut-2.3.0 → qcut-2.4.0}/docs/_static/images/circ8.png +0 -0
  98. {qcut-2.3.0 → qcut-2.4.0}/docs/_static/images/circ9.png +0 -0
  99. {qcut-2.3.0 → qcut-2.4.0}/docs/_static/images/diagram/qcut-diagram-left-logo.png +0 -0
  100. {qcut-2.3.0 → qcut-2.4.0}/docs/_static/images/logos/qcut-banner-transparent.png +0 -0
  101. {qcut-2.3.0 → qcut-2.4.0}/docs/_static/images/logos/qcut-banner-transparent.svg +0 -0
  102. {qcut-2.3.0 → qcut-2.4.0}/docs/_static/images/logos/qcut-banner.png +0 -0
  103. {qcut-2.3.0 → qcut-2.4.0}/docs/_static/images/logos/qcut-banner.svg +0 -0
  104. {qcut-2.3.0 → qcut-2.4.0}/docs/_static/images/logos/qcut-banner@2x.png +0 -0
  105. {qcut-2.3.0 → qcut-2.4.0}/docs/_static/images/logos/qcut-logo-1024.png +0 -0
  106. {qcut-2.3.0 → qcut-2.4.0}/docs/_static/images/logos/qcut-logo-2048.png +0 -0
  107. {qcut-2.3.0 → qcut-2.4.0}/docs/_static/images/logos/qcut-logo-transparent-2048.png +0 -0
  108. {qcut-2.3.0 → qcut-2.4.0}/docs/_static/images/logos/qcut-logo-transparent.svg +0 -0
  109. {qcut-2.3.0 → qcut-2.4.0}/docs/_static/images/logos/qcut-logo.svg +0 -0
  110. {qcut-2.3.0 → qcut-2.4.0}/docs/_static/images/probs1.png +0 -0
  111. {qcut-2.3.0 → qcut-2.4.0}/docs/_templates/page-toc.html +0 -0
  112. {qcut-2.3.0 → qcut-2.4.0}/docs/conf.py +0 -0
  113. {qcut-2.3.0 → qcut-2.4.0}/docs/examples/AutomaticCuts.rst +0 -0
  114. {qcut-2.3.0 → qcut-2.4.0}/docs/examples/GateCuts.rst +0 -0
  115. {qcut-2.3.0 → qcut-2.4.0}/docs/examples/WireCuts.rst +0 -0
  116. {qcut-2.3.0 → qcut-2.4.0}/docs/index.rst +0 -0
  117. {qcut-2.3.0 → qcut-2.4.0}/docs/make.bat +0 -0
  118. {qcut-2.3.0 → qcut-2.4.0}/docs/theory/CZ_derivation.md +0 -0
  119. {qcut-2.3.0 → qcut-2.4.0}/docs/theory/General_2q_derivation.md +0 -0
  120. {qcut-2.3.0 → qcut-2.4.0}/docs/theory/Joint_rotation_derivation.md +0 -0
  121. {qcut-2.3.0 → qcut-2.4.0}/docs/theory/LOCC_wire_derivation.md +0 -0
  122. {qcut-2.3.0 → qcut-2.4.0}/docs/theory/Probability_reconstruction.md +0 -0
  123. {qcut-2.3.0 → qcut-2.4.0}/docs/theory/SWAP_derivation.md +0 -0
  124. {qcut-2.3.0 → qcut-2.4.0}/docs/theory/Wire_derivation.md +0 -0
  125. {qcut-2.3.0 → qcut-2.4.0}/setup.cfg +0 -0
  126. {qcut-2.3.0 → qcut-2.4.0}/tag-from-pipeline.sh +0 -0
  127. {qcut-2.3.0 → qcut-2.4.0}/tests/__init__.py +0 -0
  128. {qcut-2.3.0 → qcut-2.4.0}/tests/solutions_1q.py +0 -0
  129. {qcut-2.3.0 → qcut-2.4.0}/tests/solutions_automatic_cuts.py +0 -0
  130. {qcut-2.3.0 → qcut-2.4.0}/tests/test_1q_QCut.py +0 -0
  131. {qcut-2.3.0 → qcut-2.4.0}/tests/test_automatic_cuts.py +0 -0
  132. {qcut-2.3.0 → qcut-2.4.0}/tests/test_consolidate.py +0 -0
  133. {qcut-2.3.0 → qcut-2.4.0}/tests/test_distribution.py +0 -0
  134. {qcut-2.3.0 → qcut-2.4.0}/tests/test_gate_cut.py +0 -0
  135. {qcut-2.3.0 → qcut-2.4.0}/tests/test_generic_gate_cuts.py +0 -0
  136. {qcut-2.3.0 → qcut-2.4.0}/tests/test_iqm_backend.py +0 -0
  137. {qcut-2.3.0 → qcut-2.4.0}/tests/test_joint_cuts.py +0 -0
  138. {qcut-2.3.0 → qcut-2.4.0}/tests/test_locc_wire_cuts.py +0 -0
  139. {qcut-2.3.0 → qcut-2.4.0}/tests/test_new_gate_cuts.py +0 -0
  140. {qcut-2.3.0 → qcut-2.4.0}/tests/test_observables.py +0 -0
  141. {qcut-2.3.0 → qcut-2.4.0}/tests/test_parallel.py +0 -0
  142. {qcut-2.3.0 → qcut-2.4.0}/tests/test_probabilities.py +0 -0
  143. {qcut-2.3.0 → qcut-2.4.0}/tests/test_qcut_backend_utils.py +0 -0
  144. {qcut-2.3.0 → qcut-2.4.0}/tests/test_qcuterror.py +0 -0
  145. {qcut-2.3.0 → qcut-2.4.0}/tests/test_qpd_analytic.py +0 -0
  146. {qcut-2.3.0 → qcut-2.4.0}/tests/test_qpd_generate.py +0 -0
  147. {qcut-2.3.0 → qcut-2.4.0}/tests/test_sampling.py +0 -0
  148. {qcut-2.3.0 → qcut-2.4.0}/uv.lock +0 -0
@@ -2,6 +2,27 @@
2
2
  Changelog
3
3
  =========
4
4
 
5
+ **Version 2.4.0**
6
+ =================
7
+
8
+ Submitting without blocking
9
+ ---------------------------
10
+
11
+ - New :code:`submit_experiments()`, now the recommended way to run an experiment. It
12
+ takes the same arguments as :code:`run_experiments()` and returns a :code:`QCutJob`
13
+ once everything is submitted, without waiting for results. Its :code:`job_ids()`,
14
+ :code:`status()`, :code:`statuses()`, :code:`done()`, :code:`cancel()` and
15
+ :code:`result()` follow Qiskit's job interface, and :code:`partial_results()` reports
16
+ each batch's wave, job id, status, circuit and shot count, and result once it is in.
17
+ - :code:`run_experiments()` is now :code:`submit_experiments(...).result()`. Its
18
+ signature and return value are unchanged.
19
+ - Experiments whose wire cuts communicate return just as quickly. Wave zero is
20
+ submitted before :code:`submit_experiments()` returns, and a thread of the job's own
21
+ submits each later wave as soon as the one before it is in.
22
+ - A target refusing a batch part-way through no longer loses the jobs already queued.
23
+ Nothing after it is submitted, the job reports :code:`ERROR`, and :code:`result()`
24
+ raises the refusal.
25
+
5
26
  **Version 2.3.0**
6
27
  =================
7
28
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: QCut
3
- Version: 2.3.0
3
+ Version: 2.4.0
4
4
  Summary: QCut is a quantum circuit knitting package built on top of qiskit for performing gate cuts and resetless wire cuts allowing simulation of larger quantum circuits on smaller quantum devices or simulators at the cost of a circuit overhead.
5
5
  Author-email: Joonas Nivala <joonas.nivala@csc.fi>, FiQCI <servicedesk@csc.fi>
6
6
  License: Apache License
@@ -496,7 +496,7 @@ print(block.gamma, local.gamma, local.optimal_gamma)
496
496
  `7.0 16.0 7.0`
497
497
 
498
498
  Those cuts run in waves, since one side has to be measured before the other can prepare
499
- what it measured. `run_experiments()` handles that itself.
499
+ what it measured. `submit_experiments()` handles that itself.
500
500
 
501
501
  ### Options
502
502
 
@@ -565,10 +565,23 @@ routed in for you. See
565
565
  ## Execution
566
566
 
567
567
  ```python
568
- results = ck.run_experiments(cut_experiment, backend=fake)
569
- expectation_values = ck.estimate_expectation_values(results)
568
+ job = ck.submit_experiments(cut_experiment, backend=fake)
569
+ expectation_values = ck.estimate_expectation_values(job.result())
570
570
  ```
571
571
 
572
+ `submit_experiments()` returns a `QCutJob` as soon as everything is submitted, and
573
+ `result()` waits for the results. The job is shaped like a Qiskit job, so it can be
574
+ checked on while the run is under way:
575
+
576
+ ```python
577
+ job.job_ids() # every backend job, in submission order
578
+ job.status() # one JobStatus for the whole experiment
579
+ job.partial_results() # per batch: wave, job id, status, circuits, shots, result
580
+ ```
581
+
582
+ `run_experiments()` takes the same arguments and is the same as
583
+ `submit_experiments(...).result()`.
584
+
572
585
  The cost of an experiment run can be checked before submission:
573
586
 
574
587
  ```python
@@ -591,13 +604,18 @@ Exact for non-cummunicating runs. Runs whose wire cuts communicate get executed
591
604
  each wave after the first spends its shots on what the one before it measured. For these jobs
592
605
  the circuit count is exact and job and shot counts are estimates.
593
606
 
607
+ The job submits those waves itself. The first goes out before `submit_experiments()`
608
+ returns, and a thread of the job's own submits each later one as soon as the one before
609
+ it is in, so nothing has to keep polling for the run to move on. `job.cancel()` cancels
610
+ what is still queued and submits no further waves.
611
+
594
612
  `backend` takes a Qiskit backend, a V2 sampler, or anything else shaped like a backend,
595
613
  which is what lets e.g. [fiqci-ems](https://github.com/FiQCI/fiqci-ems) run the experiment:
596
614
 
597
615
  ```python
598
616
  from fiqci.ems import FiQCISampler
599
617
 
600
- results = ck.run_experiments(
618
+ job = ck.submit_experiments(
601
619
  cut_experiment, backend=FiQCISampler(backend, mitigation_level=1)
602
620
  )
603
621
  ```
@@ -681,12 +699,16 @@ instead of `observables`:
681
699
 
682
700
  ```python
683
701
  cut_experiment = ck.get_experiment_circuits(cut_circuit, qubits=[0, 1])
684
- results = ck.run_experiments(cut_experiment, shots=4096, backend=sim)
702
+ job = ck.submit_experiments(cut_experiment, shots=4096, backend=sim)
685
703
 
686
- probs = ck.estimate_probabilities(results)
704
+ probs = ck.estimate_probabilities(job.result())
705
+ print(probs)
687
706
  ```
688
707
 
689
- `{'00': 0.8658, '01': -0.0024, '10': 0.1041, '11': 0.0324}`
708
+ `QuasiProbabilities({'00': 0.8658, '01': -0.0024, '10': 0.1041, '11': 0.0324})`
709
+
710
+ The bitstrings are read with the first of the chosen qubits as the rightmost character, so
711
+ with `qubits=[2, 0]` qubit 2 is the last character and qubit 0 the first.
690
712
 
691
713
  `run()` and `run_cut_circuit()` take `qubits` in the same way, and hand back the
692
714
  distribution rather than expectation values.
@@ -696,27 +718,17 @@ would have been for a single observable however many qubits are asked for. See
696
718
  [the derivation](https://jooniv.github.io/QCut/theory/Probability_reconstruction.html)
697
719
  for how the distribution is reconstructed.
698
720
 
699
- The result is a mapping, so it indexes and plots like a dict, and carries three views:
700
-
701
- ```python
702
- probs["00"] # 0.8658
703
- probs.quasi_probabilities() # the same values, as a plain dict
704
- probs.nearest_probabilities() # closest true distribution, negatives projected away
705
- probs.counts() # scaled by the shots the experiment ran at
706
- probs.counts(shots=1000) # or by any other shots
707
- ```
708
-
709
- The three dict views report, by default, the ten most likely bitstrings. Pass `top` for a different
710
- number, or `top=None` for all of them:
721
+ The distribution is never built as a table of `2**k` values unless asked for, so the
722
+ recommended way to read it is `top()`, which finds the most likely bitstrings directly:
711
723
 
712
724
  ```python
713
- probs.quasi_probabilities(top=50) # the fifty most likely
714
- probs.counts(shots=1000, top=None) # the whole distribution, as before
725
+ probs.top(10) # {'00': 0.8658, '10': 0.1041, '11': 0.0324, '01': -0.0024}
715
726
  ```
716
727
 
717
- Note that :code:`counts()` sums to :code:`shots` only with :code:`top=None` and
718
- Only :code:`quasi_probabilities()` gets cheaper this way since the other two project onto the
719
- nearest physical distribution first.
728
+ `probability_of()` and `marginal()` give one bitstring or a subset of the qubits.
729
+ `nearest_probabilities()` and `counts()` give the closest true distribution, as
730
+ probabilities or scaled to shots. They report the ten most likely bitstrings, or all of
731
+ them with `top=None`.
720
732
 
721
733
  Against the same circuit run whole:
722
734
 
@@ -19,7 +19,9 @@ from QCut.execution.circuit_knitting import (
19
19
  run,
20
20
  run_cut_circuit,
21
21
  run_experiments,
22
+ submit_experiments,
22
23
  )
24
+ from QCut.execution.job import PartialBatch, QCutJob
23
25
  from QCut.execution.observables import ObservablesLike
24
26
  from QCut.execution.parallel import ParallelBackend
25
27
  from QCut.execution.postprocess import (
@@ -53,6 +55,9 @@ __all__ = [
53
55
  "run",
54
56
  "run_cut_circuit",
55
57
  "run_experiments",
58
+ "submit_experiments",
59
+ "QCutJob",
60
+ "PartialBatch",
56
61
  "cut",
57
62
  "cutCZ",
58
63
  "cutSWAP",