void testComplexFunction() {
    int numParticles = 5;
    System system;
    for (int i = 0; i < numParticles; i++)
        system.addParticle(2.0);
    vector<double> table(20);
    for (int i = 0; i < 20; i++)
        table[i] = sin(0.11*i);

    // When every group contains only one particle, a CustomCentroidBondForce is identical to a
    // CustomCompoundBondForce.  Use that to test a complicated energy function with lots of terms.

    CustomCompoundBondForce* compound = new CustomCompoundBondForce(4, "x1+y2+z4+fn(distance(p1,p2))*angle(p3,p2,p4)+scale*dihedral(p2,p1,p4,p3)");
    CustomCentroidBondForce* centroid = new CustomCentroidBondForce(4, "x1+y2+z4+fn(distance(g1,g2))*angle(g3,g2,g4)+scale*dihedral(g2,g1,g4,g3)");
    compound->addGlobalParameter("scale", 0.5);
    centroid->addGlobalParameter("scale", 0.5);
    compound->addTabulatedFunction("fn", new Continuous1DFunction(table, -1, 10));
    centroid->addTabulatedFunction("fn", new Continuous1DFunction(table, -1, 10));

    // Add two bonds to the CustomCompoundBondForce.

    vector<int> particles(4);
    vector<double> parameters;
    particles[0] = 0;
    particles[1] = 1;
    particles[2] = 2;
    particles[3] = 3;
    compound->addBond(particles, parameters);
    particles[0] = 2;
    particles[1] = 4;
    particles[2] = 3;
    particles[3] = 1;
    compound->addBond(particles, parameters);

    // Add identical bonds to the CustomCentroidBondForce.  As a stronger test, make sure that
    // group number is different from particle number.

    vector<int> groupMembers(1);
    groupMembers[0] = 3;
    centroid->addGroup(groupMembers);
    groupMembers[0] = 0;
    centroid->addGroup(groupMembers);
    groupMembers[0] = 1;
    centroid->addGroup(groupMembers);
    groupMembers[0] = 2;
    centroid->addGroup(groupMembers);
    groupMembers[0] = 4;
    centroid->addGroup(groupMembers);
    vector<int> groups(4);
    groups[0] = 1;
    groups[1] = 2;
    groups[2] = 3;
    groups[3] = 0;
    centroid->addBond(groups, parameters);
    groups[0] = 3;
    groups[1] = 4;
    groups[2] = 0;
    groups[3] = 2;
    centroid->addBond(groups, parameters);

    // Add both forces as different force groups, and create a context.

    centroid->setForceGroup(1);
    system.addForce(compound);
    system.addForce(centroid);
    VerletIntegrator integrator(0.01);
    Context context(system, integrator, platform);

    // Evaluate the force and energy for various positions and see if they match.

    OpenMM_SFMT::SFMT sfmt;
    init_gen_rand(0, sfmt);
    vector<Vec3> positions(numParticles);
    for (int i = 0; i < 10; i++) {
        for (int j = 0; j < numParticles; j++)
            positions[j] = Vec3(5.0*genrand_real2(sfmt), 5.0*genrand_real2(sfmt), 5.0*genrand_real2(sfmt));
        context.setPositions(positions);
        State state1 = context.getState(State::Forces | State::Energy, false, 1<<0);
        State state2 = context.getState(State::Forces | State::Energy, false, 1<<1);
        ASSERT_EQUAL_TOL(state1.getPotentialEnergy(), state2.getPotentialEnergy(), TOL);
        for (int i = 0; i < numParticles; i++)
            ASSERT_EQUAL_VEC(state1.getForces()[i], state2.getForces()[i], TOL);
    }
}
void* CustomCompoundBondForceProxy::deserialize(const SerializationNode& node) const {
    int version = node.getIntProperty("version");
    if (version < 1 || version > 3)
        throw OpenMMException("Unsupported version number");
    CustomCompoundBondForce* force = NULL;
    try {
        CustomCompoundBondForce* force = new CustomCompoundBondForce(node.getIntProperty("particles"), node.getStringProperty("energy"));
        force->setForceGroup(node.getIntProperty("forceGroup", 0));
        if (version > 1)
            force->setUsesPeriodicBoundaryConditions(node.getBoolProperty("usesPeriodic"));
        const SerializationNode& perBondParams = node.getChildNode("PerBondParameters");
        for (auto& parameter : perBondParams.getChildren())
            force->addPerBondParameter(parameter.getStringProperty("name"));
        const SerializationNode& globalParams = node.getChildNode("GlobalParameters");
        for (auto& parameter : globalParams.getChildren())
            force->addGlobalParameter(parameter.getStringProperty("name"), parameter.getDoubleProperty("default"));
        if (version > 2) {
            const SerializationNode& energyDerivs = node.getChildNode("EnergyParameterDerivatives");
            for (auto& parameter : energyDerivs.getChildren())
                force->addEnergyParameterDerivative(parameter.getStringProperty("name"));
        }
        const SerializationNode& bonds = node.getChildNode("Bonds");
        vector<int> particles(force->getNumParticlesPerBond());
        vector<double> params(force->getNumPerBondParameters());
        for (auto& bond : bonds.getChildren()) {
            for (int j = 0; j < (int) particles.size(); j++) {
                stringstream key;
                key << "p";
                key << j+1;
                particles[j] = bond.getIntProperty(key.str());
            }
            for (int j = 0; j < (int) params.size(); j++) {
                stringstream key;
                key << "param";
                key << j+1;
                params[j] = bond.getDoubleProperty(key.str());
            }
            force->addBond(particles, params);
        }
        const SerializationNode& functions = node.getChildNode("Functions");
        for (auto& function : functions.getChildren()) {
            if (function.hasProperty("type")) {
                force->addTabulatedFunction(function.getStringProperty("name"), function.decodeObject<TabulatedFunction>());
            }
            else {
                // This is an old file created before TabulatedFunction existed.
                
                const SerializationNode& valuesNode = function.getChildNode("Values");
                vector<double> values;
                for (auto& child : valuesNode.getChildren())
                    values.push_back(child.getDoubleProperty("v"));
                force->addTabulatedFunction(function.getStringProperty("name"), new Continuous1DFunction(values, function.getDoubleProperty("min"), function.getDoubleProperty("max")));
            }
        }
        return force;
    }
    catch (...) {
        if (force != NULL)
            delete force;
        throw;
    }
}