Skip to content

Commit 846d4ad

Browse files
FEAT: implement quantum problem set filter (#287)
* FEAT: implement `dict_set_intersection()` * FEAT: implement `filter_quantum_number_problem_set()`
1 parent c156ea3 commit 846d4ad

File tree

6 files changed

+303
-3
lines changed

6 files changed

+303
-3
lines changed

docs/conf.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ def pick_newtype_attrs(some_type: type) -> list:
4747
add_module_names = False
4848
api_github_repo = f"{ORGANIZATION}/{REPO_NAME}"
4949
api_target_substitutions: dict[str, str | tuple[str, str]] = {
50+
"EdgeQuantumNumberTypes": ("obj", "qrules.quantum_numbers.EdgeQuantumNumberTypes"),
5051
"EdgeType": "typing.TypeVar",
5152
"GraphEdgePropertyMap": ("obj", "qrules.argument_handling.GraphEdgePropertyMap"),
5253
"GraphElementProperties": ("obj", "qrules.solving.GraphElementProperties"),
@@ -56,11 +57,13 @@ def pick_newtype_attrs(some_type: type) -> list:
5657
"NewEdgeType": "typing.TypeVar",
5758
"NewNodeType": "typing.TypeVar",
5859
"NodeQuantumNumber": ("obj", "qrules.quantum_numbers.NodeQuantumNumber"),
60+
"NodeQuantumNumberTypes": ("obj", "qrules.quantum_numbers.NodeQuantumNumberTypes"),
5961
"NodeType": "typing.TypeVar",
6062
"ParticleWithSpin": ("obj", "qrules.particle.ParticleWithSpin"),
6163
"Path": "pathlib.Path",
6264
"qrules.topology.EdgeType": "typing.TypeVar",
6365
"qrules.topology.NodeType": "typing.TypeVar",
66+
"Rule": ("obj", "qrules.argument_handling.Rule"),
6467
"SpinFormalism": ("obj", "qrules.transition.SpinFormalism"),
6568
"StateDefinition": ("obj", "qrules.combinatorics.StateDefinition"),
6669
"StateTransition": ("obj", "qrules.transition.StateTransition"),

docs/usage/visualize.ipynb

Lines changed: 118 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,16 @@
5050
"```"
5151
]
5252
},
53+
{
54+
"cell_type": "markdown",
55+
"metadata": {},
56+
"source": [
57+
":::{warning}\n",
58+
"Currently the main user-interface is the ```StateTransitionManager```. There is work in progress to remove it and split its functionality into several functions/classes to separate concerns\n",
59+
"and to facilitate the modification of intermediate results like the filtering of ```QNProblemSet```s, setting allowed interaction types, etc. (see below)\n",
60+
":::"
61+
]
62+
},
5363
{
5464
"cell_type": "markdown",
5565
"metadata": {},
@@ -103,7 +113,18 @@
103113
"from IPython.display import display\n",
104114
"\n",
105115
"import qrules\n",
116+
"from qrules.conservation_rules import (\n",
117+
" parity_conservation,\n",
118+
" spin_magnitude_conservation,\n",
119+
" spin_validity,\n",
120+
")\n",
106121
"from qrules.particle import Spin\n",
122+
"from qrules.quantum_numbers import EdgeQuantumNumbers, NodeQuantumNumbers\n",
123+
"from qrules.solving import (\n",
124+
" CSPSolver,\n",
125+
" dict_set_intersection,\n",
126+
" filter_quantum_number_problem_set,\n",
127+
")\n",
107128
"from qrules.topology import create_isobar_topologies, create_n_body_topology\n",
108129
"from qrules.transition import State"
109130
]
@@ -315,6 +336,102 @@
315336
"graphviz.Source(dot)"
316337
]
317338
},
339+
{
340+
"cell_type": "markdown",
341+
"metadata": {},
342+
"source": [
343+
"### Filtering quantum number problem sets"
344+
]
345+
},
346+
{
347+
"cell_type": "markdown",
348+
"metadata": {},
349+
"source": [
350+
"Sometimes, only a certain subset of quantum numbers and conservation rules are relevant, or the number of solutions the {class}`.StateTransitionManager` gives by default is too large for the follow-up analysis.\n",
351+
"The {func}`.filter_quantum_number_problem_set` function can be used to produce a {class}`.QNProblemSet` where only the desired quantum numbers and conservation rules are considered when fed back to the solver."
352+
]
353+
},
354+
{
355+
"cell_type": "code",
356+
"execution_count": null,
357+
"metadata": {},
358+
"outputs": [],
359+
"source": [
360+
"desired_edge_properties = {EdgeQuantumNumbers.spin_magnitude, EdgeQuantumNumbers.parity}\n",
361+
"desired_node_properties = {\n",
362+
" NodeQuantumNumbers.l_magnitude,\n",
363+
" NodeQuantumNumbers.s_magnitude,\n",
364+
"} # has to be reused in the CSPSolver-constructor\n",
365+
"filtered_qn_problem_set = filter_quantum_number_problem_set(\n",
366+
" qn_problem_set,\n",
367+
" edge_rules={spin_validity},\n",
368+
" node_rules={spin_magnitude_conservation, parity_conservation},\n",
369+
" edge_properties=desired_edge_properties,\n",
370+
" node_properties=desired_node_properties,\n",
371+
")"
372+
]
373+
},
374+
{
375+
"cell_type": "code",
376+
"execution_count": null,
377+
"metadata": {
378+
"jupyter": {
379+
"source_hidden": true
380+
},
381+
"tags": [
382+
"hide-output"
383+
]
384+
},
385+
"outputs": [],
386+
"source": [
387+
"dot = qrules.io.asdot(filtered_qn_problem_set, render_node=True)\n",
388+
"graphviz.Source(dot)"
389+
]
390+
},
391+
{
392+
"cell_type": "markdown",
393+
"metadata": {},
394+
"source": [
395+
":::{warning}\n",
396+
"The next cell will use some (currently) internal functionality. As statet at the top, a workflow similar to this will be used in future versions of ```qrules```. Manual setup of the {obj}`.CSPSolver` like in here will then also not be necessary.\n",
397+
":::"
398+
]
399+
},
400+
{
401+
"cell_type": "code",
402+
"execution_count": null,
403+
"metadata": {},
404+
"outputs": [],
405+
"source": [
406+
"solver = CSPSolver([\n",
407+
" dict_set_intersection(\n",
408+
" qrules.system_control.create_edge_properties(part),\n",
409+
" desired_edge_properties,\n",
410+
" )\n",
411+
" for part in qrules.particle.load_pdg()\n",
412+
"])\n",
413+
"\n",
414+
"filtered_qn_solutions = solver.find_solutions(filtered_qn_problem_set)\n",
415+
"filtered_qn_result = filtered_qn_solutions.solutions[6]"
416+
]
417+
},
418+
{
419+
"cell_type": "code",
420+
"execution_count": null,
421+
"metadata": {
422+
"jupyter": {
423+
"source_hidden": true
424+
},
425+
"tags": [
426+
"hide-input"
427+
]
428+
},
429+
"outputs": [],
430+
"source": [
431+
"dot = qrules.io.asdot(filtered_qn_result, render_node=True)\n",
432+
"graphviz.Source(dot)"
433+
]
434+
},
318435
{
319436
"cell_type": "markdown",
320437
"metadata": {},
@@ -672,7 +789,7 @@
672789
"name": "python",
673790
"nbconvert_exporter": "python",
674791
"pygments_lexer": "ipython3",
675-
"version": "3.11.7"
792+
"version": "3.9.20"
676793
}
677794
},
678795
"nbformat": 4,

src/qrules/argument_handling.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
Scalar = Union[int, float]
2626

2727
Rule = Union[GraphElementRule, EdgeQNConservationRule, ConservationRule]
28+
"""Any type of rule"""
2829

2930
_ElementType = TypeVar("_ElementType")
3031

src/qrules/quantum_numbers.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,6 @@ class EdgeQuantumNumbers:
104104
edge_qn_type.__module__ = __name__
105105

106106

107-
# for static typing
108107
EdgeQuantumNumber = Union[
109108
EdgeQuantumNumbers.pid,
110109
EdgeQuantumNumbers.mass,
@@ -126,8 +125,8 @@ class EdgeQuantumNumbers:
126125
EdgeQuantumNumbers.c_parity,
127126
EdgeQuantumNumbers.g_parity,
128127
]
128+
"""Type hint for quantum numbers of edges"""
129129

130-
# for accessing the keys of the dicts in EdgeSettings
131130
EdgeQuantumNumberTypes = Union[
132131
type[EdgeQuantumNumbers.pid],
133132
type[EdgeQuantumNumbers.mass],
@@ -149,6 +148,7 @@ class EdgeQuantumNumbers:
149148
type[EdgeQuantumNumbers.c_parity],
150149
type[EdgeQuantumNumbers.g_parity],
151150
]
151+
"""Type-Union for accessing the keys of the dicts in `.EdgeSettings`"""
152152

153153

154154
@frozen(init=False)
@@ -186,6 +186,7 @@ class NodeQuantumNumbers:
186186
type[NodeQuantumNumbers.s_projection],
187187
type[NodeQuantumNumbers.parity_prefactor],
188188
]
189+
"""Type-Union for accessing the keys of the dicts in `.NodeSettings`"""
189190

190191

191192
def _to_optional_float(optional_float: float | None) -> float | None:

src/qrules/solving.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,86 @@ def topology(self) -> Topology:
101101
return self.initial_facts.topology
102102

103103

104+
def filter_quantum_number_problem_set(
105+
quantum_number_problem_set: QNProblemSet,
106+
edge_rules: set[GraphElementRule],
107+
node_rules: set[Rule],
108+
edge_properties: Iterable[EdgeQuantumNumberTypes],
109+
node_properties: Iterable[NodeQuantumNumberTypes],
110+
) -> QNProblemSet:
111+
"""Filter `QNProblemSet` for desired conservation rules, settings and domains.
112+
113+
Currently it is the responsibility of the user to provide fitting properties
114+
and domains for the correspinding conservation rules.
115+
116+
Args:
117+
quantum_number_problem_set: `QNProblemSet` as generated by `CSPSolver`.
118+
edge_rules: Conservation rules regarding the edges.
119+
node_rules: Conservation rules regarding the nodes.
120+
edge_properties: Edge settings, properties and domains.
121+
node_properties: Node settings, properties and domains.
122+
"""
123+
old_edge_settings = quantum_number_problem_set.solving_settings.states
124+
old_node_settings = quantum_number_problem_set.solving_settings.interactions
125+
old_edge_properties = quantum_number_problem_set.initial_facts.states
126+
old_node_properties = quantum_number_problem_set.initial_facts.interactions
127+
new_edge_settings = {
128+
edge_id: EdgeSettings(
129+
conservation_rules=edge_rules,
130+
rule_priorities=edge_setting.rule_priorities,
131+
qn_domains=({
132+
key: val
133+
for key, val in edge_setting.qn_domains.items()
134+
if key in set(edge_properties)
135+
}),
136+
)
137+
for edge_id, edge_setting in old_edge_settings.items()
138+
}
139+
new_node_settings = {
140+
node_id: NodeSettings(
141+
conservation_rules=node_rules,
142+
rule_priorities=node_setting.rule_priorities,
143+
qn_domains=({
144+
key: val
145+
for key, val in node_setting.qn_domains.items()
146+
if key in set(node_properties)
147+
}),
148+
)
149+
for node_id, node_setting in old_node_settings.items()
150+
}
151+
new_combined_settings = MutableTransition(
152+
topology=quantum_number_problem_set.solving_settings.topology,
153+
states=new_edge_settings,
154+
interactions=new_node_settings,
155+
)
156+
new_edge_properties = {
157+
edge_id: {
158+
edge_quantum_number: scalar
159+
for edge_quantum_number, scalar in graph_edge_property_map.items()
160+
if edge_quantum_number in edge_properties
161+
}
162+
for edge_id, graph_edge_property_map in old_edge_properties.items()
163+
}
164+
new_node_properties = {
165+
node_id: {
166+
node_quantum_number: scalar
167+
for node_quantum_number, scalar in graph_node_property_map.items()
168+
if node_quantum_number in node_properties
169+
}
170+
for node_id, graph_node_property_map in old_node_properties.items()
171+
}
172+
new_combined_properties = MutableTransition(
173+
topology=quantum_number_problem_set.initial_facts.topology,
174+
states=new_edge_properties,
175+
interactions=new_node_properties,
176+
)
177+
return attrs.evolve(
178+
quantum_number_problem_set,
179+
solving_settings=new_combined_settings,
180+
initial_facts=new_combined_properties,
181+
)
182+
183+
104184
QuantumNumberSolution = MutableTransition[GraphEdgePropertyMap, GraphNodePropertyMap]
105185

106186

@@ -747,6 +827,13 @@ def __convert_solution_keys(
747827
return converted_solutions
748828

749829

830+
def dict_set_intersection(
831+
base_dict: dict[Any, Any],
832+
set_of_keys: set[Any],
833+
) -> dict[Any, Any]:
834+
return {key: value for key, value in base_dict.items() if key in set_of_keys}
835+
836+
750837
class Scoresheet:
751838
def __init__(self) -> None:
752839
self.__rule_calls: dict[tuple[int, Rule], int] = {}

0 commit comments

Comments
 (0)