|
| 1 | +# diffpy_apps MCP server: how to use it |
| 2 | + |
| 3 | +## Before starting |
| 4 | + |
| 5 | +Call `list_models()` / `list_profiles()` first (session is a shared global singleton, not per-conversation). Call `clear()` to reset. |
| 6 | + |
| 7 | +## MCP call-format rules |
| 8 | + |
| 9 | +- **Always pass explicit `model_name`/`profile_name`.** `add_equation_model`/`add_pdf_model` default `model_name` to a UUID computed once at import time — every omitted call gets the *same* value. `profile_name` defaults to `None` and gets echoed straight into the confirmation message. |
| 10 | +- **`combine_models(parent_model_name, child_model_names, symbol)`: call once per child.** `symbol` is one required string, no default. Use `symbol` = the child's own name, unless deliberately renaming an equation-model child (never a PDF child — mismatches there just log a warning, nothing renames). Only an equation model can be a parent. |
| 11 | +- **`add_pdf_model(model_name=..., from_model_name=<existing>)` shares structure, not values.** Lattice/xyz/ADP(Uiso or Biso)/occupancy become the *same* live object as the source — set them once, on the source, never per clone. Generator params (`scale`, `qdamp`, `qbroad`, `delta1`, `delta2`) are NOT shared — set each clone's separately or tie them with a `solve` constraint. Constrain symmetry on the source **before** cloning. |
| 12 | +- **`solve`**: `profile_names`/`model_names`/`residual_equations`/`weights` must be equal length, one entry per contribution. `constraints` is exactly two dicts: `[0]` helper-variable -> initial value, `[1]` variable -> constraint-equation string. Pass `name=` to inspect later via `list_recipe_parameters`. `include_sgpars=true` auto-adds symmetry-freed structural params instead of listing each one. |
| 13 | +- **Parsing results**: `check_profile_meta`/`list_profiles`/`list_models` -> plain JSON. `get_variable`/`list_model_parameters`/`list_recipe_parameters` -> formatted text (`"Variable 'x': 0.42"`), parse it yourself. `get_model_evaluation`/`get_model_residual` write a JSON array to `data_path`; `get_profile_data` writes `{"xobs":[...], "yobs":[...]}` to `data_path` (no `dyobs`/calculated curve). `solve` returns a fit-report string. Errors come back as `"{ExceptionType}: message"`. |
| 14 | + |
| 15 | +## Case A — nested equation models (no structure, e.g. fitting `A*sin(a*x)`) |
| 16 | + |
| 17 | +```jsonc |
| 18 | +add_profile_from_arrays(xarray=[...], yarray=[...], profile_name="sine_profile") |
| 19 | +add_equation_model(model_name="sub", equation_str="a*x") |
| 20 | +add_equation_model(model_name="main", equation_str="A*sin(u)") |
| 21 | +combine_models(parent_model_name="main", child_model_names=["sub"], symbol="u") // "u" renames sub into the parent equation |
| 22 | +set_variables_value(name_value_dict={"main.A": 0.8, "main.sub.a": 0.5}) |
| 23 | +solve(profile_names=["sine_profile"], model_names=["main"], |
| 24 | + variable_names=["main.A", "main.sub.a"], name="sine_fit") |
| 25 | +get_variable(variable_name="main.A") |
| 26 | +get_variable(variable_name="main.sub.a") |
| 27 | +``` |
| 28 | + |
| 29 | +## Case B — single PDF model (e.g. Ni), optionally wrapped for a free scale |
| 30 | + |
| 31 | +```jsonc |
| 32 | +add_profile_from_file(profile_path="Ni.gr", profile_name="ni_profile") |
| 33 | +update_profile_meta(profile_name="ni_profile", meta={"qmin": 0.1}) |
| 34 | +set_profile_calculation_range(profile_name="ni_profile", xmin=1.5, xmax=20, dx=0.01) |
| 35 | + |
| 36 | +add_pdf_model(model_name="pdf", structure_file_path="Ni.cif") |
| 37 | +constrain_pdf_model_space_group_symmetry(model_name="pdf") // omit space_group to auto-detect |
| 38 | +set_variables_value(name_value_dict={"pdf.scale": 0.4, "pdf.delta2": 2, "pdf.qdamp": 0.04, "pdf.qbroad": 0.02}) |
| 39 | +solve(profile_names=["ni_profile"], model_names=["pdf"], |
| 40 | + variable_names=["pdf.scale", "pdf.delta2", "pdf.qdamp", "pdf.qbroad"], |
| 41 | + include_sgpars=true, name="ni_fit") |
| 42 | + |
| 43 | +// -- optional: promote to a free scale factor via a wrapping equation model -- |
| 44 | +set_variables_value(name_value_dict={"pdf.scale": 1}) // freeze pdf's own scale at 1 |
| 45 | +add_equation_model(model_name="ni_model", equation_str="s*pdf") |
| 46 | +combine_models(parent_model_name="ni_model", child_model_names=["pdf"], symbol="pdf") |
| 47 | +set_variables_value(name_value_dict={"ni_model.s": 0.4}) |
| 48 | +solve(profile_names=["ni_profile"], model_names=["ni_model"], |
| 49 | + variable_names=["ni_model.s", "pdf.delta2", "pdf.qdamp", "pdf.qbroad"], |
| 50 | + include_sgpars=true, name="ni_fit_scaled") |
| 51 | +get_variable(variable_name="ni_model.pdf.phase.lattice.a") // combined child addressed as parent.child.param |
| 52 | +``` |
| 53 | + |
| 54 | +## Case C — multi-contribution joint refinement (Ni x-ray + Ni neutron + Si x-ray + mixed Si–Ni x-ray) |
| 55 | + |
| 56 | +```jsonc |
| 57 | +add_profile_from_file(profile_path="ni-q27r60-xray.gr", profile_name="ni_xray") |
| 58 | +add_profile_from_file(profile_path="ni-q27r100-neutron.gr", profile_name="ni_neutron") |
| 59 | +add_profile_from_file(profile_path="si-q27r60-xray.gr", profile_name="si_xray") |
| 60 | +add_profile_from_file(profile_path="si90ni10-q27r60-xray.gr", profile_name="total_xray") |
| 61 | +set_profile_calculation_range(profile_name="ni_xray", xmax=20) |
| 62 | +set_profile_calculation_range(profile_name="ni_neutron", xmax=20) |
| 63 | +set_profile_calculation_range(profile_name="si_xray", xmax=20) |
| 64 | +set_profile_calculation_range(profile_name="total_xray", xmax=20) |
| 65 | + |
| 66 | +add_pdf_model(model_name="pdf_ni", structure_file_path="Ni.cif") |
| 67 | +constrain_pdf_model_space_group_symmetry(model_name="pdf_ni") // constrain BEFORE cloning |
| 68 | +add_pdf_model(model_name="pdf_ni_neutron", from_model_name="pdf_ni") |
| 69 | +add_pdf_model(model_name="pdf_ni_partial", from_model_name="pdf_ni") |
| 70 | + |
| 71 | +add_pdf_model(model_name="pdf_si", structure_file_path="Si.cif") |
| 72 | +constrain_pdf_model_space_group_symmetry(model_name="pdf_si") |
| 73 | +add_pdf_model(model_name="pdf_si_partial", from_model_name="pdf_si") |
| 74 | + |
| 75 | +add_equation_model(model_name="main", equation_str="scale * (pdf_ni_partial + pdf_si_partial)") |
| 76 | +combine_models(parent_model_name="main", child_model_names=["pdf_ni_partial"], symbol="pdf_ni_partial") |
| 77 | +combine_models(parent_model_name="main", child_model_names=["pdf_si_partial"], symbol="pdf_si_partial") |
| 78 | + |
| 79 | +set_variables_value(name_value_dict={ |
| 80 | + "pdf_ni.qdamp": 0.055, "pdf_ni_neutron.qdamp": 0.030, "pdf_ni_partial.qdamp": 0.052, |
| 81 | + "pdf_si.qdamp": 0.051, "pdf_si_partial.qdamp": 0.052, |
| 82 | + "main.scale": 1.0, "pdf_si.scale": 1.0, "pdf_ni.scale": 1.0 |
| 83 | +}) |
| 84 | + |
| 85 | +solve( |
| 86 | + profile_names=["ni_xray", "ni_neutron", "si_xray", "total_xray"], |
| 87 | + model_names=["pdf_ni", "pdf_ni_neutron", "pdf_si", "main"], |
| 88 | + residual_equations=["resv", "resv", "resv", "resv"], |
| 89 | + variable_names=[ |
| 90 | + "pdf_ni.scale", "pdf_si.scale", "pdf_ni_neutron.scale", "main.scale", "pscale", |
| 91 | + "pdf_ni.phase.lattice.a", "pdf_ni.phase.Ni0.Uiso", "pdf_si.phase.a", "pdf_si.phase.Si.Biso", |
| 92 | + "ni_delta2", "si_delta2" |
| 93 | + ], |
| 94 | + constraints=[ |
| 95 | + {"pscale": 0.8, "ni_delta2": 2.5, "si_delta2": 2.5}, |
| 96 | + { |
| 97 | + "pdf_ni.delta2": "ni_delta2", "pdf_ni_neutron.delta2": "ni_delta2", "main.pdf_ni_partial.delta2": "ni_delta2", |
| 98 | + "pdf_si.delta2": "si_delta2", "main.pdf_si_partial.delta2": "si_delta2", |
| 99 | + "main.pdf_si_partial.scale": "1 - pscale", "main.pdf_ni_partial.scale": "pscale" |
| 100 | + } |
| 101 | + ], |
| 102 | + name="multi_fit" |
| 103 | +) |
| 104 | + |
| 105 | +get_variable(variable_name="pdf_ni.phase.lattice.a") |
| 106 | +get_variable(variable_name="pdf_ni.phase.Ni0.Uiso") // Biso equivalent = value * 8 * pi^2 |
| 107 | +get_variable(variable_name="pdf_si.phase.Si.Biso") |
| 108 | +``` |
| 109 | + |
| 110 | +Notes specific to this case: `pdf_ni`/`pdf_ni_neutron`/`pdf_ni_partial` share one structure (clones), so only `pdf_ni.phase.*` needs to be in `variable_names`. `delta2` is generator-level and NOT shared, hence the explicit constraints tying every clone's `delta2` back to one free `ni_delta2`/`si_delta2`. `pscale`/`1 - pscale` splits the mixed `total_xray` contribution's intensity between the Ni and Si partials. |
| 111 | + |
| 112 | +## Unverified — check before relying on |
| 113 | + |
| 114 | +- Exact pairing of `weights`/`restraints`/`metas` in `solve`. |
| 115 | +- Whether the live connected `add_pdf_model` accepts `structure_lib` ("Diffpy"/"PyObjcryst") — seen on one live schema fetch but absent from the server source reviewed here. |
0 commit comments