qoro-divi 0.2.0b1__py3-none-any.whl → 0.6.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.
Files changed (92) hide show
  1. divi/__init__.py +1 -2
  2. divi/backends/__init__.py +10 -0
  3. divi/backends/_backend_properties_conversion.py +227 -0
  4. divi/backends/_circuit_runner.py +70 -0
  5. divi/backends/_execution_result.py +70 -0
  6. divi/backends/_parallel_simulator.py +486 -0
  7. divi/backends/_qoro_service.py +663 -0
  8. divi/backends/_qpu_system.py +101 -0
  9. divi/backends/_results_processing.py +133 -0
  10. divi/circuits/__init__.py +13 -0
  11. divi/{exp/cirq → circuits/_cirq}/__init__.py +1 -2
  12. divi/circuits/_cirq/_parser.py +110 -0
  13. divi/circuits/_cirq/_qasm_export.py +78 -0
  14. divi/circuits/_core.py +391 -0
  15. divi/{qasm.py → circuits/_qasm_conversion.py} +73 -14
  16. divi/circuits/_qasm_validation.py +694 -0
  17. divi/qprog/__init__.py +27 -8
  18. divi/qprog/_expectation.py +181 -0
  19. divi/qprog/_hamiltonians.py +281 -0
  20. divi/qprog/algorithms/__init__.py +16 -0
  21. divi/qprog/algorithms/_ansatze.py +368 -0
  22. divi/qprog/algorithms/_custom_vqa.py +263 -0
  23. divi/qprog/algorithms/_pce.py +262 -0
  24. divi/qprog/algorithms/_qaoa.py +579 -0
  25. divi/qprog/algorithms/_vqe.py +262 -0
  26. divi/qprog/batch.py +387 -74
  27. divi/qprog/checkpointing.py +556 -0
  28. divi/qprog/exceptions.py +9 -0
  29. divi/qprog/optimizers.py +1014 -43
  30. divi/qprog/quantum_program.py +243 -412
  31. divi/qprog/typing.py +62 -0
  32. divi/qprog/variational_quantum_algorithm.py +1208 -0
  33. divi/qprog/workflows/__init__.py +10 -0
  34. divi/qprog/{_graph_partitioning.py → workflows/_graph_partitioning.py} +139 -95
  35. divi/qprog/workflows/_qubo_partitioning.py +221 -0
  36. divi/qprog/workflows/_vqe_sweep.py +560 -0
  37. divi/reporting/__init__.py +7 -0
  38. divi/reporting/_pbar.py +127 -0
  39. divi/reporting/_qlogger.py +68 -0
  40. divi/reporting/_reporter.py +155 -0
  41. {qoro_divi-0.2.0b1.dist-info → qoro_divi-0.6.0.dist-info}/METADATA +43 -15
  42. qoro_divi-0.6.0.dist-info/RECORD +47 -0
  43. {qoro_divi-0.2.0b1.dist-info → qoro_divi-0.6.0.dist-info}/WHEEL +1 -1
  44. qoro_divi-0.6.0.dist-info/licenses/LICENSES/.license-header +3 -0
  45. divi/_pbar.py +0 -73
  46. divi/circuits.py +0 -139
  47. divi/exp/cirq/_lexer.py +0 -126
  48. divi/exp/cirq/_parser.py +0 -889
  49. divi/exp/cirq/_qasm_export.py +0 -37
  50. divi/exp/cirq/_qasm_import.py +0 -35
  51. divi/exp/cirq/exception.py +0 -21
  52. divi/exp/scipy/_cobyla.py +0 -342
  53. divi/exp/scipy/pyprima/LICENCE.txt +0 -28
  54. divi/exp/scipy/pyprima/__init__.py +0 -263
  55. divi/exp/scipy/pyprima/cobyla/__init__.py +0 -0
  56. divi/exp/scipy/pyprima/cobyla/cobyla.py +0 -599
  57. divi/exp/scipy/pyprima/cobyla/cobylb.py +0 -849
  58. divi/exp/scipy/pyprima/cobyla/geometry.py +0 -240
  59. divi/exp/scipy/pyprima/cobyla/initialize.py +0 -269
  60. divi/exp/scipy/pyprima/cobyla/trustregion.py +0 -540
  61. divi/exp/scipy/pyprima/cobyla/update.py +0 -331
  62. divi/exp/scipy/pyprima/common/__init__.py +0 -0
  63. divi/exp/scipy/pyprima/common/_bounds.py +0 -41
  64. divi/exp/scipy/pyprima/common/_linear_constraints.py +0 -46
  65. divi/exp/scipy/pyprima/common/_nonlinear_constraints.py +0 -64
  66. divi/exp/scipy/pyprima/common/_project.py +0 -224
  67. divi/exp/scipy/pyprima/common/checkbreak.py +0 -107
  68. divi/exp/scipy/pyprima/common/consts.py +0 -48
  69. divi/exp/scipy/pyprima/common/evaluate.py +0 -101
  70. divi/exp/scipy/pyprima/common/history.py +0 -39
  71. divi/exp/scipy/pyprima/common/infos.py +0 -30
  72. divi/exp/scipy/pyprima/common/linalg.py +0 -452
  73. divi/exp/scipy/pyprima/common/message.py +0 -336
  74. divi/exp/scipy/pyprima/common/powalg.py +0 -131
  75. divi/exp/scipy/pyprima/common/preproc.py +0 -393
  76. divi/exp/scipy/pyprima/common/present.py +0 -5
  77. divi/exp/scipy/pyprima/common/ratio.py +0 -56
  78. divi/exp/scipy/pyprima/common/redrho.py +0 -49
  79. divi/exp/scipy/pyprima/common/selectx.py +0 -346
  80. divi/interfaces.py +0 -25
  81. divi/parallel_simulator.py +0 -258
  82. divi/qlogger.py +0 -119
  83. divi/qoro_service.py +0 -343
  84. divi/qprog/_mlae.py +0 -182
  85. divi/qprog/_qaoa.py +0 -440
  86. divi/qprog/_vqe.py +0 -275
  87. divi/qprog/_vqe_sweep.py +0 -144
  88. divi/utils.py +0 -116
  89. qoro_divi-0.2.0b1.dist-info/RECORD +0 -58
  90. /divi/{qem.py → circuits/qem.py} +0 -0
  91. {qoro_divi-0.2.0b1.dist-info → qoro_divi-0.6.0.dist-info/licenses}/LICENSE +0 -0
  92. {qoro_divi-0.2.0b1.dist-info → qoro_divi-0.6.0.dist-info/licenses}/LICENSES/Apache-2.0.txt +0 -0
@@ -0,0 +1,101 @@
1
+ # SPDX-FileCopyrightText: 2025 Qoro Quantum Ltd <divi@qoroquantum.de>
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ """Data models for Quantum Processing Units (QPUs) and QPUSystems."""
6
+
7
+ from __future__ import annotations
8
+
9
+ from dataclasses import dataclass, field, replace
10
+ from threading import RLock
11
+
12
+ _AVAILABLE_QPU_SYSTEMS: dict[str, QPUSystem] = {}
13
+ _CACHE_LOCK = RLock()
14
+
15
+
16
+ @dataclass(frozen=True, repr=True)
17
+ class QPU:
18
+ """Represents a single Quantum Processing Unit (QPU).
19
+
20
+ Attributes:
21
+ nickname: The unique name or identifier for the QPU.
22
+ q_bits: The number of qubits in the QPU.
23
+ status: The current operational status of the QPU.
24
+ system_kind: The type of technology the QPU uses.
25
+ """
26
+
27
+ nickname: str
28
+ q_bits: int
29
+ status: str
30
+ system_kind: str
31
+
32
+
33
+ @dataclass(frozen=True, repr=True)
34
+ class QPUSystem:
35
+ """Represents a collection of QPUs that form a quantum computing system.
36
+
37
+ Attributes:
38
+ name: The name of the QPU system.
39
+ qpus: A list of QPU objects that are part of this system.
40
+ access_level: The access level granted to the user for this system (e.g., 'PUBLIC').
41
+ supports_expval: Whether the system supports expectation value jobs.
42
+ """
43
+
44
+ name: str
45
+ qpus: list[QPU] = field(default_factory=list)
46
+ access_level: str = "PUBLIC"
47
+ supports_expval: bool = False
48
+
49
+
50
+ def parse_qpu_systems(json_data: list) -> list[QPUSystem]:
51
+ """Parses a list of QPU system data from JSON into QPUSystem objects."""
52
+ return [
53
+ QPUSystem(
54
+ name=system_data["name"],
55
+ qpus=[QPU(**qpu) for qpu in system_data.get("qpus", [])],
56
+ access_level=system_data["access_level"],
57
+ )
58
+ for system_data in json_data
59
+ ]
60
+
61
+
62
+ def update_qpu_systems_cache(systems: list[QPUSystem]):
63
+ """Updates the cache of available QPU systems."""
64
+ with _CACHE_LOCK:
65
+ _AVAILABLE_QPU_SYSTEMS.clear()
66
+ for system in systems:
67
+ if system.name == "qoro_maestro":
68
+ system = replace(system, supports_expval=True)
69
+ _AVAILABLE_QPU_SYSTEMS[system.name] = system
70
+
71
+
72
+ def get_qpu_system(name: str) -> QPUSystem:
73
+ """
74
+ Get a QPUSystem object by its name from the cache.
75
+
76
+ Args:
77
+ name: The name of the QPU system to retrieve.
78
+
79
+ Returns:
80
+ The QPUSystem object with the matching name.
81
+
82
+ Raises:
83
+ ValueError: If the cache is empty or the system is not found.
84
+ """
85
+ with _CACHE_LOCK:
86
+ if not _AVAILABLE_QPU_SYSTEMS:
87
+ raise ValueError(
88
+ "QPU systems cache is empty. Call `QoroService.fetch_qpu_systems()` to populate it."
89
+ )
90
+ try:
91
+ return _AVAILABLE_QPU_SYSTEMS[name]
92
+ except KeyError:
93
+ raise ValueError(
94
+ f"QPUSystem with name '{name}' not found in cache."
95
+ ) from None
96
+
97
+
98
+ def get_available_qpu_systems() -> list[QPUSystem]:
99
+ """Returns a list of all available QPU systems from the cache."""
100
+ with _CACHE_LOCK:
101
+ return list(_AVAILABLE_QPU_SYSTEMS.values())
@@ -0,0 +1,133 @@
1
+ # SPDX-FileCopyrightText: 2025 Qoro Quantum Ltd <divi@qoroquantum.de>
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ import base64
6
+
7
+
8
+ def _decode_qh1_b64(encoded: dict) -> dict[str, int]:
9
+ """
10
+ Decode a {'encoding':'qh1','n_bits':N,'payload':base64} histogram
11
+ into a dict with bitstring keys -> int counts.
12
+
13
+ If `encoded` is None, returns None.
14
+ If `encoded` is an empty dict or has a missing/empty payload, returns `encoded` unchanged.
15
+ Otherwise, decodes the payload and returns a dict mapping bitstrings to counts.
16
+ """
17
+ if not encoded or not encoded.get("payload"):
18
+ return encoded
19
+
20
+ if encoded.get("encoding") != "qh1":
21
+ raise ValueError(f"Unsupported encoding: {encoded.get('encoding')}")
22
+
23
+ blob = base64.b64decode(encoded["payload"])
24
+ hist_int = _decompress_histogram(blob)
25
+ return {str(k): v for k, v in hist_int.items()}
26
+
27
+
28
+ def _uleb128_decode(data: bytes, pos: int = 0) -> tuple[int, int]:
29
+ x = 0
30
+ shift = 0
31
+ while True:
32
+ if pos >= len(data):
33
+ raise ValueError("truncated varint")
34
+ b = data[pos]
35
+ pos += 1
36
+ x |= (b & 0x7F) << shift
37
+ if (b & 0x80) == 0:
38
+ break
39
+ shift += 7
40
+ return x, pos
41
+
42
+
43
+ def _int_to_bitstr(x: int, n_bits: int) -> str:
44
+ return format(x, f"0{n_bits}b")
45
+
46
+
47
+ def _rle_bool_decode(data: bytes, pos=0) -> tuple[list[bool], int]:
48
+ num_runs, pos = _uleb128_decode(data, pos)
49
+ if num_runs == 0:
50
+ return [], pos
51
+ first_val = data[pos] != 0
52
+ pos += 1
53
+ total, val = [], first_val
54
+ for _ in range(num_runs):
55
+ ln, pos = _uleb128_decode(data, pos)
56
+ total.extend([val] * ln)
57
+ val = not val
58
+ return total, pos
59
+
60
+
61
+ def _decompress_histogram(buf: bytes) -> dict[str, int]:
62
+ if not buf:
63
+ return {}
64
+ pos = 0
65
+ if buf[pos : pos + 3] != b"QH1":
66
+ raise ValueError("bad magic")
67
+ pos += 3
68
+ n_bits = buf[pos]
69
+ pos += 1
70
+ unique, pos = _uleb128_decode(buf, pos)
71
+ total_shots, pos = _uleb128_decode(buf, pos)
72
+
73
+ num_gaps, pos = _uleb128_decode(buf, pos)
74
+ gaps = []
75
+ for _ in range(num_gaps):
76
+ g, pos = _uleb128_decode(buf, pos)
77
+ gaps.append(g)
78
+
79
+ idxs, acc = [], 0
80
+ for i, g in enumerate(gaps):
81
+ acc = g if i == 0 else acc + g
82
+ idxs.append(acc)
83
+
84
+ rb_len, pos = _uleb128_decode(buf, pos)
85
+ is_one, _ = _rle_bool_decode(buf[pos : pos + rb_len], 0)
86
+ pos += rb_len
87
+
88
+ extras_len, pos = _uleb128_decode(buf, pos)
89
+ extras = []
90
+ for _ in range(extras_len):
91
+ e, pos = _uleb128_decode(buf, pos)
92
+ extras.append(e)
93
+
94
+ counts, it = [], iter(extras)
95
+ for flag in is_one:
96
+ counts.append(1 if flag else next(it) + 2)
97
+
98
+ hist = {_int_to_bitstr(i, n_bits): c for i, c in zip(idxs, counts)}
99
+
100
+ # optional integrity check
101
+ if sum(counts) != total_shots:
102
+ raise ValueError("corrupt stream: shot sum mismatch")
103
+ if len(counts) != unique:
104
+ raise ValueError("corrupt stream: unique mismatch")
105
+ return hist
106
+
107
+
108
+ def reverse_dict_endianness(
109
+ probs_dict: dict[str, dict[str, float]],
110
+ ) -> dict[str, dict[str, float]]:
111
+ """Reverse endianness of all bitstrings in a dictionary of probability distributions."""
112
+ return {
113
+ tag: {bitstring[::-1]: prob for bitstring, prob in probs.items()}
114
+ for tag, probs in probs_dict.items()
115
+ }
116
+
117
+
118
+ def convert_counts_to_probs(
119
+ counts: dict[str, dict[str, int]], shots: int
120
+ ) -> dict[str, dict[str, float]]:
121
+ """Convert raw counts to probability distributions.
122
+
123
+ Args:
124
+ counts (dict[str, dict[str, int]]): The counts to convert to probabilities.
125
+ shots (int): The number of shots.
126
+
127
+ Returns:
128
+ dict[str, dict[str, float]]: The probability distributions.
129
+ """
130
+ return {
131
+ tag: {bitstring: count / shots for bitstring, count in probs.items()}
132
+ for tag, probs in counts.items()
133
+ }
@@ -0,0 +1,13 @@
1
+ # SPDX-FileCopyrightText: 2025-2026 Qoro Quantum Ltd <divi@qoroquantum.de>
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ from ._qasm_conversion import to_openqasm
6
+ from ._qasm_validation import is_valid_qasm, validate_qasm, validate_qasm_count_qubits
7
+ from ._core import (
8
+ CircuitBundle,
9
+ ExecutableQASMCircuit,
10
+ MetaCircuit,
11
+ CircuitTag,
12
+ format_circuit_tag,
13
+ )
@@ -2,6 +2,5 @@
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
4
 
5
- # TODO: delete whole module once Cirq properly supports parameters in openqasm 3.0
6
5
  from . import _qasm_export # Does nothing, just initiates the patch
7
- from ._qasm_import import cirq_circuit_from_qasm
6
+ from ._parser import ExtendedQasmParser
@@ -0,0 +1,110 @@
1
+ # SPDX-FileCopyrightText: 2025 Qoro Quantum Ltd <divi@qoroquantum.de>
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ from __future__ import annotations
6
+
7
+ import sympy
8
+ from cirq.contrib.qasm_import._lexer import QasmLexer
9
+ from cirq.contrib.qasm_import._parser import QasmParser
10
+ from cirq.contrib.qasm_import.exception import QasmException
11
+ from ply import yacc
12
+
13
+
14
+ class ExtendedQasmLexer(QasmLexer):
15
+ """Extended lexer with INPUT and ANGLE keywords."""
16
+
17
+ reserved = {
18
+ **QasmLexer.reserved,
19
+ "input": "INPUT",
20
+ "angle": "ANGLE",
21
+ }
22
+ # Rebuild tokens list to include new reserved keywords
23
+ tokens = [
24
+ "FORMAT_SPEC",
25
+ "NUMBER",
26
+ "NATURAL_NUMBER",
27
+ "STDGATESINC",
28
+ "QELIBINC",
29
+ "ID",
30
+ "ARROW",
31
+ "EQ",
32
+ ] + list(reserved.values())
33
+
34
+ # PLY quirk: When overriding t_ID, PLY's method discovery doesn't find inherited
35
+ # token methods. These three have multi-character patterns that MUST be checked
36
+ # before t_ID (PLY matches longest patterns first). Without explicit definitions,
37
+ # "OPENQASM" and "include" would be tokenized as ID instead of their proper types.
38
+ # We keep the regex in the docstring (required by PLY) but delegate to parent.
39
+ def t_FORMAT_SPEC(self, t):
40
+ r"""OPENQASM(\s+)([^\s\t\;]*);"""
41
+ return super().t_FORMAT_SPEC(t)
42
+
43
+ def t_QELIBINC(self, t):
44
+ r"""include(\s+)"qelib1.inc";"""
45
+ return super().t_QELIBINC(t)
46
+
47
+ def t_STDGATESINC(self, t):
48
+ r"""include(\s+)"stdgates.inc";"""
49
+ return super().t_STDGATESINC(t)
50
+
51
+ # Override t_ID to check both parent and extended reserved dicts
52
+ def t_ID(self, t):
53
+ r"""[^\W\d_][\w_]*"""
54
+ # This regex matches any Unicode letter (not digit/underscore) at the start,
55
+ # followed by any number of Unicode word characters or underscores.
56
+ # Check extended reserved first, then parent's
57
+ if t.value in self.reserved:
58
+ t.type = self.reserved[t.value]
59
+ elif t.value in QasmLexer.reserved:
60
+ t.type = QasmLexer.reserved[t.value]
61
+ return t
62
+
63
+
64
+ class ExtendedQasmParser(QasmParser):
65
+ """Extended parser with QASM 3.0 parameter support.
66
+
67
+ Only adds support for: input angle[32] param_name;
68
+ """
69
+
70
+ # Add new tokens
71
+ tokens = QasmParser.tokens + ["INPUT", "ANGLE"]
72
+
73
+ def __init__(self) -> None:
74
+ # Initialize parameter storage
75
+ self.input_params: dict[str, sympy.Symbol] = {}
76
+
77
+ # Call parent to set up everything (this will create its own lexer and parser)
78
+ super().__init__()
79
+
80
+ self.lexer = ExtendedQasmLexer()
81
+
82
+ self.parser = yacc.yacc(module=self, debug=False, write_tables=False)
83
+
84
+ # Add parser rule for input angle declarations
85
+ def p_circuit_input_angle(self, p):
86
+ """circuit : input_angle circuit"""
87
+ p[0] = self.circuit
88
+
89
+ def p_input_angle(self, p):
90
+ """input_angle : INPUT ANGLE '[' NATURAL_NUMBER ']' ID ';'"""
91
+ param_name = p[6]
92
+ self.input_params[param_name] = sympy.Symbol(param_name)
93
+ p[0] = None
94
+
95
+ # Override to check input_params first
96
+ def p_expr_identifier(self, p):
97
+ """expr : ID"""
98
+ # Check input_params first (QASM 3.0 parameters)
99
+ if p[1] in self.input_params:
100
+ p[0] = self.input_params[p[1]]
101
+ return
102
+
103
+ # Fall back to parent logic (custom gate parameters)
104
+ if not self.in_custom_gate_scope:
105
+ raise QasmException(
106
+ f"Parameter '{p[1]}' in line {p.lineno(1)} not supported"
107
+ )
108
+ if p[1] not in self.custom_gate_scoped_params:
109
+ raise QasmException(f"Undefined parameter '{p[1]}' in line {p.lineno(1)}'")
110
+ p[0] = sympy.Symbol(p[1])
@@ -0,0 +1,78 @@
1
+ # SPDX-FileCopyrightText: 2025 Qoro Quantum Ltd <divi@qoroquantum.de>
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ """
6
+ Patch for Cirq's QasmArgs.format_field to support symbolic parameters in QASM export.
7
+
8
+ This module patches Cirq's QasmArgs.format_field method to properly handle:
9
+ 1. Symbolic parameters (Sympy expressions) with "half_turns" spec for rotation gates
10
+ 2. Float precision rounding for numeric parameters
11
+ 3. Qubit ID mapping for Qid objects
12
+
13
+ Note: Measurement keys are handled directly by Cirq's qasm() function and do not
14
+ require special handling in format_field.
15
+
16
+ The patch is essential for QEM (Quantum Error Mitigation) protocols that modify circuits
17
+ with symbolic parameters. When Cirq exports QASM code using cirq.qasm(), it formats rotation
18
+ gate angles using the "half_turns" spec. Without this patch, Sympy expressions would not be
19
+ properly converted to QASM format (e.g., "theta*pi" instead of just "theta").
20
+
21
+ Example:
22
+ When a circuit contains a rotation gate with a Sympy symbol:
23
+ rz(theta * pi) q[0]
24
+
25
+ Cirq's QASM export calls format_field with the Sympy expression and spec="half_turns".
26
+ This patch ensures the expression is properly formatted for QASM output.
27
+ """
28
+
29
+ from typing import Any
30
+
31
+ from cirq import ops
32
+ from cirq.protocols.qasm import QasmArgs
33
+ from sympy import Expr, pi
34
+
35
+
36
+ def patched_format_field(self, value: Any, spec: str) -> str:
37
+ """Patched version of QasmArgs.format_field for symbolic parameter support.
38
+
39
+ This method extends Cirq's QasmArgs.format_field to handle:
40
+ - Sympy expressions with "half_turns" spec (for rotation gate angles)
41
+ - Float precision rounding
42
+ - Qubit ID mapping
43
+
44
+ Args:
45
+ self: QasmArgs instance
46
+ value: The value to format (can be float, int, Sympy Expr, or Qid)
47
+ spec: Format specifier (e.g., "half_turns" or empty string)
48
+
49
+ Returns:
50
+ Formatted string representation of the value suitable for QASM output
51
+ """
52
+ # Handle numeric values (floats and integers)
53
+ if isinstance(value, (float, int)):
54
+ if isinstance(value, float):
55
+ value = round(value, self.precision)
56
+ if spec == "half_turns":
57
+ # Format as "pi*value" for QASM rotation gates
58
+ value = f"pi*{value}" if value != 0 else "0"
59
+ spec = ""
60
+
61
+ # Handle Cirq Qid objects (qubits)
62
+ elif isinstance(value, ops.Qid):
63
+ value = self.qubit_id_map[value]
64
+
65
+ # Handle Sympy expressions (symbolic parameters)
66
+ if isinstance(value, Expr):
67
+ if spec == "half_turns":
68
+ # Multiply by pi for rotation gates (Cirq uses half-turns internally)
69
+ value *= pi
70
+ return str(value)
71
+
72
+ # Fall back to parent implementation for other cases
73
+ return super(QasmArgs, self).format_field(value, spec)
74
+
75
+
76
+ # Apply the patch to QasmArgs.format_field
77
+ # This is done at module import time so all QASM exports use the patched version
78
+ QasmArgs.format_field = patched_format_field