Skip to main content
Docs

Search guides and API endpoints, for example “Idempotency-Key” or “submit job”.

    Tools · Custom Modeling

    Thermo Property Models

    Correct Joback and Hansen estimates with your own measurements, and predict solubility and mixture properties across solvents and temperatures.

    Updated October 12, 2026

    On this page

    Prices, workflows, and method papersOpen in the app

    Thermo Property Models trains two kinds of model for fluids and thermodynamics, and saves either as a model you can predict with.

    • Physics-hybrid models start from a group-contribution estimate and learn how far your measurements are from it. A few dozen measured values remove the estimate's bias for your chemistry.
    • Mixture models learn a property of a solute in a solvent, or of a single molecule, as a function of temperature and other conditions. One prediction request screens solvents or traces a curve against temperature.

    Physics-hybrid models

    Pick up to three properties to correct:

    PropertiesEstimate
    Normal boiling point, melting point, critical temperature, pressure and volume, ideal-gas enthalpy and Gibbs energy of formation, enthalpies of vaporization and fusionJoback group contribution (as in Group-Contribution Properties)
    Hansen dispersion, polar and hydrogen-bonding parametersStefanis–Panayiotou group contribution (as in Hansen Solubility Parameters)

    Give your measured values in whatever unit you use (for example °C or K, kJ/mol or kcal/mol); the estimate is converted to it and the model predicts in it.

    For each property, the same panel of learners as QSAR Models is cross-validated on measured minus estimate, and the best one is saved. A prediction is the estimate plus the learned correction, with a 90% prediction interval. When a molecule is unlike anything the correction was trained on, the prediction is flagged; by default the model then returns the estimate alone, unless cross-validation showed the correction still beating the estimate on such molecules. You can also keep the hybrid prediction everywhere.

    The training report scores three models on the same held-out molecules: the estimate alone, the hybrid and a pure machine-learning model trained without the estimate. At small dataset sizes the hybrid usually wins clearly; with many measurements the pure model can catch up.

    Mixture models

    Upload a table with one row per measurement: the solute SMILES (and the solvent SMILES for solutions), the conditions (temperature, and optionally pressure or mole fraction, each with its unit), and one or more measured properties such as log S, an activity coefficient, a density or a viscosity. A message-passing neural network encodes each molecule and appends the conditions, so one model covers every solvent and temperature in your data.

    How the test set is drawn decides what the reported error means:

    SplitThe test set holds
    Solute (recommended for new compounds)Solutes the model never saw
    SolventSolvents the model never saw
    BothNew solutes in new solvents (rows that mix new and seen molecules are left out)
    RandomRows from solutes and solvents also seen in training (the easiest test)

    Predictions take a list of solutes, a list of solvents and condition values, such as a temperature grid in any unit, and return every combination. Each prediction comes with a 90% interval and flags for whether the solute is like the training solutes, the solvent was in training, and the conditions lie inside the training range.

    Outputs

    FileContents
    comparison.csv (physics-hybrid)The estimate, the hybrid and the pure-ML model on cross-validation and the test set, per property
    test_predictions.csv (physics-hybrid)Measured values and each model's test-set predictions
    model/The saved model with its model card (no pickled code)

    Saved models appear in My Models. Physics-hybrid models can also score whole libraries in a workflow with MPNN Models (Score library).

    Get the estimates without training from Group-Contribution Properties and Hansen Solubility Parameters; compare a mixture model's solubility curves with the ideal-solution curves of Solid–Liquid Equilibrium / Eutectic Diagram.

    Run it from the API

    Submit with Submit a job and the job_type below. Price it first with Estimate job reservation cost: submitting reserves that amount from your wallet, and the charge settles at the actual runtime.

    Physics-Hybrid Thermo Model Training train-thermo-hybrid

    Job type
    train-thermo-hybrid
    Hardware
    cpu (default)
    Typical runtime
    20 min on CPU

    Payload

    Payload fields
    FieldTypeDescription
    model_namerequiredstring

    Limits: min length 1

    properties[]requiredobject[]

    Limits: min items 1, max items 3

    properties.propertyrequiredstring

    One of: "tb", "tm", "tc", "pc", "vc", "hf", "gf", "hvap", "hfus", "delta_d", "delta_p", "delta_h"

    properties.unitstring

    One of: "K", "°C", "°F", "°R", "Pa", "kPa", "MPa", "bar", "atm", "psi", "mmHg", "m³/mol", "L/mol", "cm³/mol", "J/mol", "kJ/mol", "cal/mol", "kcal/mol", "MPa^0.5", "(J/cm³)^0.5", "(cal/cm³)^0.5"

    properties.columnstring

    Limits: min length 1

    input_data[]string[]

    Limits: min items 1, max items 10000

    input_formatstring

    Default: "smiles"One of: "smiles"

    targets[]((number | null)[])[]

    Limits: min items 1, max items 10000

    training_tablestring | object[] | artifactRef
    smiles_columnstring

    Default: "smiles"Limits: min length 1

    split_columnstring

    Limits: min length 1

    splits[]string[]

    One of: "train", "training", "val", "valid", "validation", "test"Limits: min items 1

    split_methodstring

    One of: "random", "scaffold", "cluster", "provided"

    test_sizenumber

    Default: 0.2Limits: ≥ 0, ≤ 0.5

    random_seedinteger

    Limits: ≥ 0

    features[]string[]

    Default: ["morgan_counts","rdkit2d"]One of: "morgan_counts", "rdkit2d", "maccs"Limits: min items 1

    baseline_featureboolean

    Default: true

    morgan_radiusinteger

    Default: 2One of: 1, 2, 3, 4

    morgan_bitsinteger

    Default: 2048One of: 512, 1024, 2048, 4096

    algorithms[]string[]

    One of: "linear", "random_forest", "lightgbm", "svm", "knn", "gp"Limits: min items 1

    selection_metricstring

    Default: "rmse"One of: "rmse", "mae", "r2"

    cv_foldsinteger

    Default: 5Limits: ≥ 3, ≤ 10

    fallbackstring

    Default: "baseline"One of: "baseline", "none"

    model_descriptionstring
    tags[]string[]

    Example

    from cognichem_client import CogniChem
    
    client = CogniChem.from_env()  # reads COGNICHEM_API_KEY
    payload = {
        "model_name": "thermo-hybrid-tb-tiny",
        "input_data": ["CCO", "CCCO", "CCCCO", "CCCCCO", "CC(C)O", "… 31 more"],
        "targets": [[351.4], [370.3], [390.9], [411], [355.4], "… 31 more"],
        "properties": [{"property": "tb", "unit": "K"}],
        "features": ["morgan_counts"],
        "algorithms": ["linear", "knn"],
        "cv_folds": 3,
        "test_size": 0.2,
        "random_seed": 7,
    }
    
    estimate = client.jobs.estimate(job_type="train-thermo-hybrid", payload=payload, resource="cpu")
    print(f"Reserves ${estimate.cost:.2f}")
    
    job = client.jobs.submit(
        job_name="my-train-thermo-hybrid-run",
        job_type="train-thermo-hybrid",
        payload=payload,
        resource="cpu",
    )
    status = client.jobs.wait(job.process_id)
    if status.status == "completed":
        client.jobs.result(job.process_id, save_path=".")

    Sample data from the job catalog; long values are shortened here. Each job_name must be unique among your jobs.

    Workflow inputs

    • Table (list)CSV, TSV, JSON

    Workflow outputs

    • ArchiveZIP
    • TableCSV
    • Trained modelOPAQUE

    Mixture Property Model Training train-thermo-mixture

    Job type
    train-thermo-mixture
    Hardware
    cpu (default)t4l4a10
    Typical runtime
    1 h on CPU

    Payload

    Payload fields
    FieldTypeDescription
    model_namerequiredstring

    Limits: min length 1

    training_tablestring | object[] | artifactRef
    component_columns[]string[]

    Default: ["solute_smiles","solvent_smiles"]Limits: min items 1, max items 2

    component_names[]string[]

    Limits: min items 1, max items 2

    conditions[]object[]

    Limits: max items 3

    conditions.quantityrequiredstring

    One of: "temperature", "pressure", "mole_fraction"

    conditions.unitrequiredstring

    One of: "K", "°C", "°F", "°R", "Pa", "kPa", "MPa", "bar", "atm", "psi", "mmHg", "mol/mol"

    conditions.columnstring

    Limits: min length 1

    target_specs[]requiredobject[]

    Limits: min items 1, max items 5

    target_specs.columnstring

    Limits: min length 1

    target_specs.namestring

    Limits: min length 1

    target_specs.unitstring
    input_data[]string[][]

    Limits: min items 10, max items 20000

    input_formatstring

    Default: "smiles"One of: "smiles"

    conditions_data[]number[][]
    targets[]((number | null)[])[]
    split_columnstring

    Limits: min length 1

    splits[]string[]

    One of: "train", "training", "val", "valid", "validation", "test"Limits: min items 1

    model_paramsobject
    model_params.validation_sizenumber

    Default: 0.1Limits: ≥ 0.01, ≤ 0.5

    model_params.test_sizenumber

    Default: 0.1Limits: ≥ 0, ≤ 0.5

    model_params.ensemble_sizeinteger

    Default: 1Limits: ≥ 1, ≤ 10

    model_params.mp_hidden_diminteger

    Default: 300Limits: ≥ 16, ≤ 2048

    model_params.mp_depthinteger

    Default: 3Limits: ≥ 1, ≤ 10

    model_params.n_ffn_layersinteger

    Default: 2Limits: ≥ 1, ≤ 10

    model_params.ffn_hidden_diminteger

    Default: 300Limits: ≥ 16, ≤ 4096

    model_params.learning_ratenumber

    Default: 0.001Limits: ≥ 0.000001, ≤ 0.1

    model_params.n_epochsinteger

    Default: 50Limits: ≥ 6, ≤ 1000

    model_params.batch_sizeinteger

    Default: 64Limits: ≥ 1, ≤ 512

    model_params.featuresetstring

    Default: "organic"One of: "organic", "v1", "v2"

    model_params.split_methodstring

    One of: "random", "solute", "solvent", "both", "scaffold", "provided"

    model_params.random_seedinteger | null

    Limits: ≥ 0

    model_params.shared_encoderboolean

    Default: false

    model_descriptionstring
    tags[]string[]

    Example

    from cognichem_client import CogniChem
    
    client = CogniChem.from_env()  # reads COGNICHEM_API_KEY
    payload = {
        "model_name": "thermo-mixture-logs-tiny",
        "training_table": "solute_smiles,solvent_smiles,T_C,logS\nc1ccc2ccccc2c1,CCO,25.… (808 characters)",
        "component_columns": ["solute_smiles", "solvent_smiles"],
        "conditions": [{"quantity": "temperature", "unit": "°C", "column": "T_C"}],
        "target_specs": [{"column": "logS", "unit": "log10(mol/L)"}],
        "model_params": {
            "n_epochs": 6,
            "batch_size": 8,
            "mp_hidden_dim": 32,
            "ffn_hidden_dim": 32,
            "random_seed": 3,
            "split_method": "random",
            "validation_size": 0.2,
            "test_size": 0.2,
        },
    }
    
    estimate = client.jobs.estimate(job_type="train-thermo-mixture", payload=payload, resource="cpu")
    print(f"Reserves ${estimate.cost:.2f}")
    
    job = client.jobs.submit(
        job_name="my-train-thermo-mixture-run",
        job_type="train-thermo-mixture",
        payload=payload,
        resource="cpu",
    )
    status = client.jobs.wait(job.process_id)
    if status.status == "completed":
        client.jobs.result(job.process_id, save_path=".")

    Sample data from the job catalog; long values are shortened here. Each job_name must be unique among your jobs.

    Workflow inputs

    • Table (list)CSV, TSV, JSON

    Workflow outputs

    • ArchiveZIP
    • Trained modelOPAQUE