diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 042e18d..b7c9387 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,7 +27,7 @@ jobs: - label: Py-max python-version: "3.12" runs-on: ubuntu-latest - pytest: "-k 'not (he4 and (3b or 4b))'" + pytest: "-k 'not (he4 and (3b or 4b) and not sio)'" name: "🐍 ${{ matrix.cfg.python-version }} • ${{ matrix.cfg.label }} • ${{ matrix.cfg.runs-on }}" runs-on: ${{ matrix.cfg.runs-on }} diff --git a/qcmanybody/qcng_computer.py b/qcmanybody/qcng_computer.py index 4bde73d..c5e2faa 100644 --- a/qcmanybody/qcng_computer.py +++ b/qcmanybody/qcng_computer.py @@ -47,7 +47,8 @@ def plan(self): # v2: ) class Config: extra = "allow" - frozen = False + # v2: frozen = False + allow_mutation = True class AtomicComputerQCNG(BaseComputerQCNG): @@ -155,8 +156,10 @@ class ManyBodyComputerQCNG(BaseComputerQCNG): ) embedding_charges: Dict[int, List[float]] = Field( {}, - description="Atom-centered point charges to be used on molecule fragments whose basis sets are not included in " - "the computation. Keys: 1-based index of fragment. Values: list of atom charges for that fragment.", + description="Atom-centered point charges to be used to speed up nbody-level convergence. Charges are placed on " + "molecule fragments whose basis sets are not included in the computation. (An implication is that charges aren't " + "invoked for bsse_type=cp.) Keys: 1-based index of fragment. Values: list of atom charges for that fragment.", + # TODO: enforce point charge sum == fragment_charges val json_schema_extra={ "shape": ["nfr", ""], }, @@ -190,7 +193,10 @@ class ManyBodyComputerQCNG(BaseComputerQCNG): description=ManyBodyKeywords.__fields__["supersystem_ie_only"].field_info.description, ) task_list: Dict[str, Any] = {} #MBETaskComputers] = {} - #qcmb_calculator: Optional[Any] = None + qcmb_calculator: Optional[Any] = Field( + None, + description="Low-level interface", + ) # TODO @computed_field(description="Number of distinct fragments comprising full molecular supersystem.") @property @@ -216,9 +222,10 @@ def set_bsse_type(cls, v: Any) -> List[BsseEnum]: # v2: def set_embedding_charges(cls, v: Any, info: FieldValidationInfo) -> Dict[int, List[float]]: def set_embedding_charges(cls, v, values): # -> Dict[int, List[float]]: print(f"hit embedding_charges validator with {v}", end="") + nfr = len(values["molecule"].fragments) # v2: if len(v) != info.data["nfragments"]: - if len(v) != len(values["molecule"].fragments): - raise ValueError(f"embedding_charges dict should have entries for each 1-indexed fragment ({len(values['molecule'].fragments)}).") + if len(v) != nfr: + raise ValueError(f"embedding_charges dict should have entries for each 1-indexed fragment ({nfr})") print(f" ... setting embedding_charges={v}") return v @@ -396,7 +403,7 @@ def from_manybodyinput(cls, input_model: ManyBodyInput, build_tasks: bool = True specifications[mtd]["specification"].pop("schema_name", None) specifications[mtd]["specification"].pop("protocols", None) - calculator_cls = ManyBodyCalculator( + computer_model.qcmb_calculator = ManyBodyCalculator( computer_model.molecule, computer_model.bsse_type, comp_levels, @@ -406,11 +413,11 @@ def from_manybodyinput(cls, input_model: ManyBodyInput, build_tasks: bool = True ) if not build_tasks: - return computer_model, calculator_cls + return computer_model component_results = {} - for chem, label, imol in calculator_cls.iterate_molecules(): + for chem, label, imol in computer_model.qcmb_calculator.iterate_molecules(): inp = AtomicInput(molecule=imol, **specifications[chem]["specification"]) if imol.extras.get("embedding_charges"): # or test on self.embedding_charges ? @@ -445,7 +452,7 @@ def from_manybodyinput(cls, input_model: ManyBodyInput, build_tasks: bool = True pprint.pprint(component_results, width=200) print("start to analyze") - analyze_back = calculator_cls.analyze(component_results) + analyze_back = computer_model.qcmb_calculator.analyze(component_results) analyze_back["nbody_number"] = len(component_results) print("\n<<< (ZZ 3) QCEngine harness ManyBodyComputerQCNG.from_qcschema_ben analyze_back >>>") pprint.pprint(analyze_back, width=200) @@ -461,11 +468,6 @@ def compute(self, client: Optional["qcportal.FractalClient"] = None) -> None: NOTE: client logic removed (compared to psi4.driver.ManyBodyComputer) """ -# qcng: from psi4.driver.p4util import banner - -# qcng: info = "\n" + banner(f" ManyBody Computations ", strNotOutfile=True) + "\n" - #logger.info(info) - for t in self.task_list.values(): t.compute(client=client) diff --git a/qcmanybody/tests/test_mbe_he4_multilevel.py b/qcmanybody/tests/test_mbe_he4_multilevel.py index 24225a5..3d123a4 100644 --- a/qcmanybody/tests/test_mbe_he4_multilevel.py +++ b/qcmanybody/tests/test_mbe_he4_multilevel.py @@ -896,7 +896,8 @@ def test_count_he4_multi(mbe_keywords, ref_count, he_tetramer, request): atomic_spec = AtomicSpecification(model={"method": "mp2", "basis": "mybas"}, program="myqc", driver="energy") mbe_model = ManyBodyInput(specification={"specification": atomic_spec, "keywords": mbe_keywords, "driver": "energy"}, molecule=he_tetramer) - _, ret = ManyBodyComputerQCNG.from_manybodyinput(mbe_model, build_tasks=False) + ret = ManyBodyComputerQCNG.from_manybodyinput(mbe_model, build_tasks=False) + ret = ret.qcmb_calculator text, dcount = ret.format_calc_plan() assert compare_recursive(ref_count["all"], dcount, atol=1.e-6) diff --git a/qcmanybody/tests/test_mbe_he4_singlelevel.py b/qcmanybody/tests/test_mbe_he4_singlelevel.py index 0a2d147..e3ad867 100644 --- a/qcmanybody/tests/test_mbe_he4_singlelevel.py +++ b/qcmanybody/tests/test_mbe_he4_singlelevel.py @@ -708,7 +708,8 @@ def test_count_he4_single(mbe_keywords, ref_count, he_tetramer): atomic_spec = AtomicSpecification(model={"method": "mp2", "basis": "mybas"}, program="myqc", driver="energy") mbe_model = ManyBodyInput(specification={"specification": atomic_spec, "keywords": mbe_keywords, "driver": "energy"}, molecule=he_tetramer) - _, ret = ManyBodyComputerQCNG.from_manybodyinput(mbe_model, build_tasks=False) + ret = ManyBodyComputerQCNG.from_manybodyinput(mbe_model, build_tasks=False) + ret = ret.qcmb_calculator text, dcount = ret.format_calc_plan() assert compare_recursive(ref_count["all"], dcount, atol=1.e-6) diff --git a/qcmanybody/tests/test_mbe_keywords.py b/qcmanybody/tests/test_mbe_keywords.py index f894e57..d543479 100644 --- a/qcmanybody/tests/test_mbe_keywords.py +++ b/qcmanybody/tests/test_mbe_keywords.py @@ -103,7 +103,7 @@ def test_mbe_rtd(mbe_data, driver, kws, ans): mbe_data["specification"]["keywords"] = kws input_model = ManyBodyInput(**mbe_data) - comp_model, _ = ManyBodyComputerQCNG.from_manybodyinput(input_model, build_tasks=False) + comp_model = ManyBodyComputerQCNG.from_manybodyinput(input_model, build_tasks=False) assert comp_model.driver == driver assert comp_model.return_total_data == ans @@ -150,7 +150,7 @@ def test_mbe_level_bodies(mbe_data, kws, ans): mbe_data["specification"]["keywords"] = kws input_model = ManyBodyInput(**mbe_data) - comp_model, _ = ManyBodyComputerQCNG.from_manybodyinput(input_model, build_tasks=False) + comp_model = ManyBodyComputerQCNG.from_manybodyinput(input_model, build_tasks=False) assert comp_model.nfragments == 3 assert comp_model.max_nbody == ans[0] @@ -178,7 +178,7 @@ def test_mbe_level_5mer(mbe_data, kws, ans): mbe_data["specification"]["keywords"] = kws input_model = ManyBodyInput(**mbe_data) - comp_model, _ = ManyBodyComputerQCNG.from_manybodyinput(input_model, build_tasks=False) + comp_model = ManyBodyComputerQCNG.from_manybodyinput(input_model, build_tasks=False) assert comp_model.nfragments == 5 assert comp_model.max_nbody == ans[0] @@ -228,7 +228,7 @@ def test_mbe_bsse_type(mbe_data, kws, ans): return input_model = ManyBodyInput(**mbe_data) - comp_model, _ = ManyBodyComputerQCNG.from_manybodyinput(input_model, build_tasks=False) + comp_model = ManyBodyComputerQCNG.from_manybodyinput(input_model, build_tasks=False) assert comp_model.bsse_type == ans, f"{comp_model=} != {ans}" @@ -256,7 +256,7 @@ def test_mbe_sie(mbe_data, kws, ans): return input_model = ManyBodyInput(**mbe_data) - comp_model, _ = ManyBodyComputerQCNG.from_manybodyinput(input_model, build_tasks=False) + comp_model = ManyBodyComputerQCNG.from_manybodyinput(input_model, build_tasks=False) assert comp_model.supersystem_ie_only == ans