Skip to content

io

cif

handler

Minimal CIF tag handler used by descriptors/parameters.

CifHandler

Canonical CIF handler used by descriptors/parameters.

Holds CIF tags (names) and attaches to an owning descriptor so it can derive a stable uid if needed.

Source code in src/easydiffraction/io/cif/handler.py
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class CifHandler:
    """Canonical CIF handler used by descriptors/parameters.

    Holds CIF tags (names) and attaches to an owning descriptor so it
    can derive a stable uid if needed.
    """

    def __init__(self, *, names: list[str]) -> None:
        self._names = names
        self._owner = None  # set by attach

    def attach(self, owner):
        """Attach to a descriptor or parameter instance."""
        self._owner = owner

    @property
    def names(self) -> list[str]:
        """List of CIF tag names associated with the owner."""
        return self._names

    @property
    def uid(self) -> str | None:
        """Unique identifier taken from the owner, if attached."""
        if self._owner is None:
            return None
        return self._owner.unique_name
attach(owner)

Attach to a descriptor or parameter instance.

Source code in src/easydiffraction/io/cif/handler.py
19
20
21
def attach(self, owner):
    """Attach to a descriptor or parameter instance."""
    self._owner = owner
names property

List of CIF tag names associated with the owner.

uid property

Unique identifier taken from the owner, if attached.

serialize

analysis_to_cif(analysis)

Render analysis metadata, aliases, and constraints to CIF.

Source code in src/easydiffraction/io/cif/serialize.py
184
185
186
187
188
189
190
191
192
193
194
195
def analysis_to_cif(analysis) -> str:
    """Render analysis metadata, aliases, and constraints to CIF."""
    cur_min = format_value(analysis.current_minimizer)
    lines: list[str] = []
    lines.append(f'_analysis.calculator_engine  {format_value(analysis.current_calculator)}')
    lines.append(f'_analysis.fitting_engine  {cur_min}')
    lines.append(f'_analysis.fit_mode  {format_value(analysis.fit_mode)}')
    lines.append('')
    lines.append(analysis.aliases.as_cif)
    lines.append('')
    lines.append(analysis.constraints.as_cif)
    return '\n'.join(lines)

category_collection_to_cif(collection)

Render a CategoryCollection-like object to CIF text.

Uses first item to build loop header, then emits rows for each item.

Source code in src/easydiffraction/io/cif/serialize.py
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
def category_collection_to_cif(collection) -> str:
    """Render a CategoryCollection-like object to CIF text.

    Uses first item to build loop header, then emits rows for each item.
    """
    if not len(collection):
        return ''

    lines: list[str] = []

    # Header
    first_item = list(collection.values())[0]
    lines.append('loop_')
    for p in first_item.parameters:
        tags = p._cif_handler.names  # type: ignore[attr-defined]
        lines.append(tags[0])

    # Rows
    for item in collection.values():
        row_vals = [format_value(p.value) for p in item.parameters]
        lines.append(' '.join(row_vals))

    return '\n'.join(lines)

category_item_from_cif(self, block, idx=0)

Populate each parameter from CIF block at given loop index.

Source code in src/easydiffraction/io/cif/serialize.py
231
232
233
234
def category_item_from_cif(self, block, idx: int = 0) -> None:
    """Populate each parameter from CIF block at given loop index."""
    for param in self.parameters:
        param.from_cif(block, idx=idx)

category_item_to_cif(item)

Render a CategoryItem-like object to CIF text.

Expects item.parameters iterable of params with _cif_handler.names and value.

Source code in src/easydiffraction/io/cif/serialize.py
32
33
34
35
36
37
38
39
40
41
def category_item_to_cif(item) -> str:
    """Render a CategoryItem-like object to CIF text.

    Expects ``item.parameters`` iterable of params with
    ``_cif_handler.names`` and ``value``.
    """
    lines: list[str] = []
    for p in item.parameters:
        lines.append(param_to_cif(p))
    return '\n'.join(lines)

datablock_collection_to_cif(collection)

Render a collection of datablocks by joining their CIF blocks.

Source code in src/easydiffraction/io/cif/serialize.py
127
128
129
def datablock_collection_to_cif(collection) -> str:
    """Render a collection of datablocks by joining their CIF blocks."""
    return '\n\n'.join([block.as_cif for block in collection.values()])

datablock_item_to_cif(datablock)

Render a DatablockItem-like object to CIF text.

Emits a data_ header and then concatenates category CIF sections.

Source code in src/easydiffraction/io/cif/serialize.py
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
def datablock_item_to_cif(datablock) -> str:
    """Render a DatablockItem-like object to CIF text.

    Emits a data_ header and then concatenates category CIF sections.
    """
    # Local imports to avoid import-time cycles
    from easydiffraction.core.category import CategoryCollection
    from easydiffraction.core.category import CategoryItem

    header = f'data_{datablock._identity.datablock_entry_name}'
    parts: list[str] = [header]
    for v in vars(datablock).values():
        if isinstance(v, CategoryItem):
            parts.append(v.as_cif)
    for v in vars(datablock).values():
        if isinstance(v, CategoryCollection):
            parts.append(v.as_cif)
    return '\n\n'.join(parts)

datastore_to_cif(datastore, max_points=None)

Render a datastore to CIF text.

Expects datastore to have _cif_mapping() and attributes per mapping keys.

Source code in src/easydiffraction/io/cif/serialize.py
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
def datastore_to_cif(datastore, max_points: Optional[int] = None) -> str:
    """Render a datastore to CIF text.

    Expects ``datastore`` to have ``_cif_mapping()`` and attributes per
    mapping keys.
    """
    cif_lines: list[str] = ['loop_']

    mapping: dict[str, str] = datastore._cif_mapping()  # type: ignore[attr-defined]
    for cif_key in mapping.values():
        cif_lines.append(cif_key)

    data_arrays: list[np.ndarray] = []
    for attr_name in mapping:
        arr = getattr(datastore, attr_name, None)
        data_arrays.append(np.array([]) if arr is None else arr)

    if not data_arrays or not data_arrays[0].size:
        return ''

    n_points = len(data_arrays[0])

    def format_row(i: int) -> str:
        return ' '.join(str(data_arrays[j][i]) for j in range(len(data_arrays)))

    if max_points is not None and n_points > 2 * max_points:
        for i in range(max_points):
            cif_lines.append(format_row(i))
        cif_lines.append('...')
        for i in range(-max_points, 0):
            cif_lines.append(format_row(i))
    else:
        for i in range(n_points):
            cif_lines.append(format_row(i))

    return '\n'.join(cif_lines)

experiment_to_cif(experiment)

Render an experiment: datablock part plus measured data.

Source code in src/easydiffraction/io/cif/serialize.py
177
178
179
180
181
def experiment_to_cif(experiment) -> str:
    """Render an experiment: datablock part plus measured data."""
    block = datablock_item_to_cif(experiment)
    data = experiment.datastore.as_cif
    return f'{block}\n\n{data}' if data else block

format_value(value)

Format a single CIF value, quoting strings with whitespace.

Source code in src/easydiffraction/io/cif/serialize.py
15
16
17
18
19
def format_value(value) -> str:
    """Format a single CIF value, quoting strings with whitespace."""
    if isinstance(value, str) and (' ' in value or '\t' in value):
        return f'"{value}"'
    return str(value)

param_to_cif(param)

Render a single descriptor/parameter to a CIF line.

Expects param to expose _cif_handler.names and value.

Source code in src/easydiffraction/io/cif/serialize.py
22
23
24
25
26
27
28
29
def param_to_cif(param) -> str:
    """Render a single descriptor/parameter to a CIF line.

    Expects ``param`` to expose ``_cif_handler.names`` and ``value``.
    """
    tags: Sequence[str] = param._cif_handler.names  # type: ignore[attr-defined]
    main_key: str = tags[0]
    return f'{main_key} {format_value(param.value)}'

project_info_to_cif(info)

Render ProjectInfo to CIF text (id, title, description, dates).

Source code in src/easydiffraction/io/cif/serialize.py
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
def project_info_to_cif(info) -> str:
    """Render ProjectInfo to CIF text (id, title, description,
    dates).
    """
    name = f'{info.name}'

    title = f'{info.title}'
    if ' ' in title:
        title = f"'{title}'"

    if len(info.description) > 60:
        description = f'\n;\n{info.description}\n;'
    else:
        description = f'{info.description}'
        if ' ' in description:
            description = f"'{description}'"

    created = f"'{info._created.strftime('%d %b %Y %H:%M:%S')}'"
    last_modified = f"'{info._last_modified.strftime('%d %b %Y %H:%M:%S')}'"

    return (
        f'_project.id               {name}\n'
        f'_project.title            {title}\n'
        f'_project.description      {description}\n'
        f'_project.created          {created}\n'
        f'_project.last_modified    {last_modified}'
    )

project_to_cif(project)

Render a whole project by concatenating sections when present.

Source code in src/easydiffraction/io/cif/serialize.py
161
162
163
164
165
166
167
168
169
170
171
172
173
174
def project_to_cif(project) -> str:
    """Render a whole project by concatenating sections when present."""
    parts: list[str] = []
    if hasattr(project, 'info'):
        parts.append(project.info.as_cif)
    if getattr(project, 'sample_models', None):
        parts.append(project.sample_models.as_cif)
    if getattr(project, 'experiments', None):
        parts.append(project.experiments.as_cif)
    if getattr(project, 'analysis', None):
        parts.append(project.analysis.as_cif())
    if getattr(project, 'summary', None):
        parts.append(project.summary.as_cif())
    return '\n\n'.join([p for p in parts if p])

summary_to_cif(_summary)

Render a summary CIF block (placeholder for now).

Source code in src/easydiffraction/io/cif/serialize.py
198
199
200
def summary_to_cif(_summary) -> str:
    """Render a summary CIF block (placeholder for now)."""
    return 'To be added...'