Placement functions#

pasted._placement#

Atom-placement algorithms (gas / chain / shell) and post-placement repulsion relaxation. No file I/O; no metrics.

pasted._placement.add_hydrogen(atoms: list[str], rng: Random) list[str][source]#

Append hydrogen atoms when H is in the pool but absent from atoms.

The number of H atoms added is: n_H = 1 + round(uniform(0, 1) × n_current × 1.2)

The original list is not modified; a new list is returned.

pasted._placement.place_chain(atoms: list[str], bond_lo: float, bond_hi: float, branch_prob: float, persist: float, rng: Random, chain_bias: float = 0.0) tuple[list[str], list[tuple[float, float, float]]][source]#

Random-walk atom-chain growth with branching and directional persistence.

Parameters:
  • atoms – Element symbols (order is preserved).

  • bond_lo – Bond-length range (Å).

  • bond_hi – Bond-length range (Å).

  • branch_prob – Probability that an atom becomes a new branch tip rather than replacing the existing tip (0 = linear, 1 = fully branched tree).

  • persist

    Directional persistence ∈ [0, 1]. A new step direction d is accepted only when dot(d, prev_dir) persist 1.

    • 0.0 → fully random (may self-tangle)

    • 0.5 → rear 120° cone excluded (default)

    • 1.0 → front hemisphere only, nearly straight chain

  • rng – Seeded random-number generator.

  • chain_bias

    Global-axis drift strength ∈ [0, 1] (default: 0.0).

    After the first bond is placed its direction becomes the bias axis. Every subsequent step direction is blended toward that axis before normalisation:

    d_biased = d + axis * chain_bias
    d_final  = d_biased / ||d_biased||
    
    • 0.0 → no bias; behaviour identical to previous versions

    • 0.3 → moderate elongation; shape_aniso ≥ 0.5 rate rises from

      ~40% to ~70% for n = 20 atoms

    • 1.0 → strong elongation; nearly rod-like for small n

    chain_bias and persist are complementary: persist controls local turn angles between consecutive bonds; chain_bias imposes a global preferred axis regardless of chain length.

Returns:

Always len(atoms) positions.

Return type:

(atoms, positions)

pasted._placement.place_gas(atoms: list[str], region: str, rng: Random) tuple[list[str], list[tuple[float, float, float]]][source]#

Place all atoms uniformly at random inside region.

No clash checking is performed — call relax_positions() afterwards.

Parameters:
  • atoms – Element symbols.

  • region"sphere:R" | "box:L" | "box:LX,LY,LZ"

  • rng – Seeded random-number generator.

Returns:

Always len(atoms) positions.

Return type:

(atoms, positions)

Raises:

ValueError – On unrecognised region spec.

pasted._placement.place_maxent(atoms: list[str], region: str, cov_scale: float, rng: Random, maxent_steps: int = 300, maxent_lr: float = 0.05, maxent_cutoff_scale: float = 2.5, trust_radius: float = 0.5, convergence_tol: float = 0.001, seed: int | None = None) tuple[list[str], list[tuple[float, float, float]]][source]#

Place atoms to maximise angular entropy subject to distance constraints.

Implements constrained maximum-entropy placement: atoms are initialised inside region at random, then iteratively repositioned so that each atom’s neighbourhood directions become as uniformly distributed over the sphere as possible — the solution to

max S = −∫ p(Ω) ln p(Ω) dΩ s.t. d_ij ≥ cov_scale × (r_i + r_j) ∀ i,j

The angular repulsion potential

U = Σ_{i} Σ_{j,k ∈ N(i), j≠k} 1 / (1 − cos θ_{jk} + ε)

is minimised by L-BFGS (m=7, Armijo backtracking) when the C++ extension _maxent_core.place_maxent_cpp is available (HAS_MAXENT_LOOP), or by steepest descent otherwise. A per-atom trust radius caps the maximum displacement per step, replacing the fixed maxent_lr unit-norm clip of the steepest-descent fallback.

After every gradient step the mandatory distance-constraint relaxation is applied (L-BFGS penalty, identical to _relax_core).

Stability measures applied per step:

  • Per-atom trust-radius clamp: the step is uniformly rescaled so no atom moves more than trust_radius Å, preventing L-BFGS overshooting.

  • Soft restoring force: atoms that drift outside the initial region radius are gently pulled back toward the centre of mass.

  • Centre-of-mass pinning: the centre of mass is re-centred to the origin after each step so the whole structure does not drift.

Parameters:
  • atoms – Element symbols.

  • region – Initial placement region: "sphere:R" | "box:L" | "box:LX,LY,LZ".

  • cov_scale – Pyykkö distance scale factor.

  • rng – Seeded random-number generator.

  • maxent_steps – Gradient-descent / L-BFGS outer iterations (default: 300).

  • maxent_lr – Learning rate used only by the Python steepest-descent fallback (default: 0.05). Ignored when the C++ loop is active.

  • maxent_cutoff_scale – Neighbour cutoff = this factor × median covalent sum (default: 2.5). Larger values include more neighbours in the angular calculation.

  • trust_radius – Per-atom maximum displacement per step (Å, default: 0.5). Used by the C++ L-BFGS loop; steepest-descent fallback uses unit-norm clip scaled by maxent_lr instead.

  • convergence_tol – Early-termination threshold: the loop stops when the RMS gradient per atom falls below this value (Å⁻¹·a.u., default: 1e-3). Set to 0 to disable early termination and always run maxent_steps iterations. Ignored by the Python steepest-descent fallback.

  • seed – Optional integer seed forwarded to the steric-clash relaxation for the coincident-atom edge case. None → non-deterministic (default).

Returns:

Always len(atoms) positions.

Return type:

(atoms, positions)

pasted._placement.place_shell(atoms: list[str], center_sym: str, coord_lo: int, coord_hi: int, shell_lo: float, shell_hi: float, tail_lo: float, tail_hi: float, rng: Random) tuple[list[str], list[tuple[float, float, float]]][source]#

Center atom at origin + coordination shell + tail atoms.

No clash checking is performed — call relax_positions() afterwards.

Parameters:
  • atoms – Element symbols; must contain at least one occurrence of center_sym.

  • center_sym – Symbol of the atom placed at the origin.

  • coord_lo – Coordination-number range (number of shell atoms).

  • coord_hi – Coordination-number range (number of shell atoms).

  • shell_lo – Shell radius range (Å).

  • shell_hi – Shell radius range (Å).

  • tail_lo – Tail bond-length range (Å).

  • tail_hi – Tail bond-length range (Å).

  • rng – Seeded random-number generator.

Returns:

Center atom is first; always len(atoms) positions.

Return type:

(ordered_atoms, positions)

pasted._placement.relax_positions(atoms: list[str], positions: list[tuple[float, float, float]], cov_scale: float, max_cycles: int = 500, *, seed: int | None = None) tuple[list[tuple[float, float, float]], bool][source]#

Resolve interatomic distance violations by L-BFGS penalty minimization.

For every pair (i, j) whose distance falls below cov_scale × (r_i + r_j) (Pyykkö single-bond covalent radii), a harmonic penalty energy is accumulated and its gradient used to drive atoms apart. The C++ path minimizes E = Σ_{i<j} ½ · max(0, threshold d_ij)² via L-BFGS; convergence is declared when E < 1 × 10⁻⁶.

When the compiled C++ extension (pasted._ext._relax_core) is available the optimization runs in native code; otherwise the pure-Python/NumPy Gauss-Seidel fallback is used transparently.

Parameters:
  • atoms – Element symbols, one per atom.

  • positions – Initial Cartesian coordinates (Å).

  • cov_scale – Scale factor applied to the sum of covalent radii.

  • max_cycles – Maximum number of L-BFGS iterations (C++ path) or Gauss-Seidel sweeps (Python fallback). The C++ solver exits early when E < 1e-6, so the limit is rarely reached for typical structures.

  • seed – Optional integer seed for the one-time pre-perturbation jitter (C++ path) or the coincident-atom RNG (Python fallback). None → non-deterministic. Pass the generator’s master seed here for full reproducibility.

Returns:

converged is False only when max_cycles was reached with violations still present; the structure is still returned and usable.

Return type:

(relaxed_positions, converged)