Skip to content

feat: add hamiltonians plugin for wavefunction operations - #227

Merged
hzhangxyz merged 1 commit into
mainfrom
dev/individual-hamiltonian
Apr 10, 2026
Merged

feat: add hamiltonians plugin for wavefunction operations#227
hzhangxyz merged 1 commit into
mainfrom
dev/individual-hamiltonian

Conversation

@hzhangxyz

Copy link
Copy Markdown
Member

Add qmp.plugins.hamiltonians subpackage with WaveFunction and Hamiltonian classes for quantum chemistry calculations. WaveFunction handles SHCI-format text file I/O with packed configurations (2-bit encoding). Hamiltonian wraps qmp's Model class for operations like apply_within, find_relative, and diagonal_term. Example included for H2 molecule.

Copilot AI review requested due to automatic review settings April 10, 2026 06:56
@hzhangxyz
hzhangxyz force-pushed the dev/individual-hamiltonian branch from a1171f9 to 6e30193 Compare April 10, 2026 06:57

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new qmp.plugins.hamiltonians module providing a WaveFunction SHCI text I/O helper (2-bit packed configs) and a Hamiltonian wrapper around qmp.models.fcidump.Model for common wavefunction-space operations, plus an H2 example dataset/script.

Changes:

  • Introduce qmp/plugins/hamiltonians.py with SHCI load/dump and apply_within/find_relative/list_relative/diagonal_term wrappers.
  • Export the new plugin module from qmp/plugins/__init__.py.
  • Add an examples/hamiltonians/ H2 FCIDUMP + wavefunction inputs/outputs and a runnable example script.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
qmp/plugins/hamiltonians.py New WaveFunction I/O + Hamiltonian wrapper for SHCI-format workflows.
qmp/plugins/init.py Exposes hamiltonians (and pyscf) via package imports/__all__.
examples/hamiltonians/h2_example.py Demonstrates loading SHCI wavefunction, Hamiltonian ops, and dumping results.
examples/hamiltonians/H2.FCIDUMP Sample integrals for H2 demonstration.
examples/hamiltonians/H2.wavefunction Sample input wavefunction for H2.
examples/hamiltonians/found.wavefunction Example “found” configuration output.
examples/hamiltonians/rel.wavefunction Example “relative configs” output.
examples/hamiltonians/result.wavefunction Example apply_within output.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +115 to +116
config = [CHAR_TO_VAL[c] for c in parts[2:]]
config_list.append(config)

Copilot AI Apr 10, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WaveFunction.load will raise a raw KeyError if the SHCI file contains an unexpected occupation token. Consider validating tokens and raising a ValueError that includes the offending token and line content/number to make debugging bad inputs easier.

Copilot uses AI. Check for mistakes.
Comment on lines +165 to +168
chars = [VAL_TO_CHAR[int(v)] for v in chars_vals]
# Tab-separated format: index, coefficient, config characters
line = f"{i}\t{coef.real.item():.8e}\t" + " ".join(chars) + "\n"
f.write(line)

Copilot AI Apr 10, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dump() writes only coef.real, discarding any imaginary component of psi. If complex amplitudes are expected to round-trip through this format, the imaginary part should be serialized too; otherwise, it would be safer to assert imag is ~0 and raise if non-zero to prevent silent data loss.

Copilot uses AI. Check for mistakes.
Comment on lines +216 to +217
psi = self.model.apply_within(source.config, source.psi, destination.config)
return WaveFunction(config=destination.config, psi=psi, orbit_number=destination.orbit_number)

Copilot AI Apr 10, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

apply_within() sets orbit_number from destination.orbit_number. If destination was created without orbit_number (None), the returned WaveFunction cannot be dumped/unpacked correctly. Consider validating that source/destination orbit_number match and falling back to source.orbit_number when destination.orbit_number is None.

Suggested change
psi = self.model.apply_within(source.config, source.psi, destination.config)
return WaveFunction(config=destination.config, psi=psi, orbit_number=destination.orbit_number)
if (
source.orbit_number is not None
and destination.orbit_number is not None
and source.orbit_number != destination.orbit_number
):
raise ValueError(
"source and destination must have matching orbit_number values"
)
orbit_number = (
destination.orbit_number
if destination.orbit_number is not None
else source.orbit_number
)
psi = self.model.apply_within(source.config, source.psi, destination.config)
return WaveFunction(config=destination.config, psi=psi, orbit_number=orbit_number)

Copilot uses AI. Check for mistakes.
Comment thread qmp/plugins/hamiltonians.py Outdated
"""
configs_exclude = exclude.config if exclude is not None else None
configs_j = self.model.find_relative(source.config, source.psi, count_selected, configs_exclude)
return WaveFunction(config=configs_j, psi=torch.ones([len(configs_j)], dtype=torch.complex128), orbit_number=source.orbit_number)

Copilot AI Apr 10, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

find_relative() creates psi as torch.ones(...) without specifying device, so if source.config is on CUDA the returned WaveFunction will have config on GPU and psi on CPU, which will break subsequent Hamiltonian operations unless the caller manually moves psi. Construct psi on configs_j.device (and typically matching source.psi.dtype) to keep tensors consistent.

Suggested change
return WaveFunction(config=configs_j, psi=torch.ones([len(configs_j)], dtype=torch.complex128), orbit_number=source.orbit_number)
psi_dtype = source.psi.dtype if source.psi is not None else torch.complex128
psi_j = torch.ones([len(configs_j)], dtype=psi_dtype, device=configs_j.device)
return WaveFunction(config=configs_j, psi=psi_j, orbit_number=source.orbit_number)

Copilot uses AI. Check for mistakes.
Comment on lines +27 to +28
result = hamiltonian.apply_within(wave.to("cuda"), found.to("cuda"))
result.dump(data_dir / "result.wavefunction")

Copilot AI Apr 10, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This example unconditionally moves tensors to CUDA (wave.to("cuda"), found.to("cuda")), which will raise on CPU-only machines. Consider selecting the device based on torch.cuda.is_available() (or keeping the example CPU-only) so it runs in more environments.

Copilot uses AI. Check for mistakes.
Comment on lines +118 to +119
# Infer orbital count from first configuration
self.orbit_number = len(config_list[0]) if config_list else 0

Copilot AI Apr 10, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the input file has no valid configuration lines, orbit_number becomes 0 and subsequent packing will produce an incorrectly shaped config tensor (pack_int on an empty 1D tensor yields 1D output, not the documented 2D (batch, packed_bits)). Consider raising an explicit error for empty inputs (or initializing config as an empty 2D uint8 tensor).

Suggested change
# Infer orbital count from first configuration
self.orbit_number = len(config_list[0]) if config_list else 0
if not config_list:
raise ValueError(f"No valid configuration lines found in wavefunction file: {path}")
# Infer orbital count from first configuration
self.orbit_number = len(config_list[0])

Copilot uses AI. Check for mistakes.

# Unpack to recover 2-bit values per orbital
config_vals = unpack_int(self.config, size=2, last_dim=self.orbit_number)

Copilot AI Apr 10, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dump() uses zip(self.psi, config_vals), which will silently truncate output if psi and config batch sizes differ. Add an explicit check that self.psi.shape[0] matches config_vals.shape[0] and raise a clear error if not.

Suggested change
if self.psi.shape[0] != config_vals.shape[0]:
raise ValueError(
f"Mismatched batch sizes: psi has {self.psi.shape[0]} entries, "
f"but config has {config_vals.shape[0]} entries"
)

Copilot uses AI. Check for mistakes.
Comment on lines +245 to +246
Wavefunction with found configurations. psi is None since only
configs are computed, not amplitudes.

Copilot AI Apr 10, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Docstring says the returned WaveFunction has psi=None, but the implementation sets psi to a ones tensor. Please align the docstring with the actual behavior (or change the return to psi=None and adjust dump()/callers accordingly) to avoid confusing API consumers.

Suggested change
Wavefunction with found configurations. psi is None since only
configs are computed, not amplitudes.
Wavefunction with found configurations and a placeholder psi
tensor of ones. Only configs are computed here; amplitudes are
not evaluated by this method.

Copilot uses AI. Check for mistakes.
@hzhangxyz
hzhangxyz force-pushed the dev/individual-hamiltonian branch 3 times, most recently from efed936 to 5da5d4b Compare April 10, 2026 07:12
Add qmp.plugins.hamiltonians subpackage with WaveFunction and Hamiltonian
classes for quantum chemistry calculations. WaveFunction handles SHCI-format
text file I/O with packed configurations (2-bit encoding). Hamiltonian wraps
qmp's Model class for operations like apply_within, find_relative, and
diagonal_term. Example included for H2 molecule.
@hzhangxyz
hzhangxyz force-pushed the dev/individual-hamiltonian branch from 5da5d4b to bcb0cb8 Compare April 10, 2026 07:17
@hzhangxyz
hzhangxyz merged commit 0bbc966 into main Apr 10, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants