Skip to Content

Sentimental Equilibrium

Models one person–app relationship. Feeling fades unless outbound effort is supplied (dx/dt = -r x + a c). The sustaining rate solves x* = (a/r) c* and a U'(x*) = (r + ρ) D'(c*).

Live Dynamic Engagement scoring does not return that rate. score() ranks offers from the response rate: feeling is the take-up, implied effort is (r/a) x, and the rank is U(x) - D(c)/(r+ρ) mixed with a UCB bonus. RollingBehavior writes that map to arm_reward. computeEquilibrium() solves the steady state and score() does not read it.

How often to contact a person is the Workbench batch at /sentimental-equilibrium. A field map turns outbound events into effort and response events into feeling, then writes recommended_engagement_rate and rate_adjustment.

Algorithm

Config value: "approach": "behaviorAlgos", "sub_approach": "sentimentalEquilibrium"

State dynamics (engagement capital \(x\) with effort \(c\)):

\(\frac{dx}{dt} = a \cdot c - r \cdot x\)

Engagement capital accumulates with effort at efficiency \(a\) and decays at rate \(r\).

Equilibrium (steady state with \(x^* = \frac{a}{r} \cdot c^*\)):

\(a \cdot U'(x^*) = (r + \rho) \cdot D'(c^*)\)

Here \(\rho\) is the discount rate (time preference). The equilibrium \((x^*, c^*)\) is found with Newton–Raphson iteration.

Utility functions (configurable via utilityType):

  • sqrt: \(U(x) = \sqrt{x}\)
  • linear: \(U(x) = x\)
  • log: \(U(x) = \ln(1 + x)\)

Disutility of effort (configurable via disutilityType):

  • quadratic: \(D(c) = \frac{1}{2}(c - c_\star)^2\). Default \(c_\star = 0\), which is \(\frac{1}{2} c^2\).
  • linear: \(D(c) = c\)

Parameters

  • decayRate (\(r\)): Rate at which engagement capital depreciates. Default: 0.1.
  • effortEfficiency (\(a\)): How effectively effort converts into engagement capital. Default: 1.0.
  • discountRate (\(\rho\)): Time preference / impatience. Default: 0.05.
  • utilityType: Shape of \(U\): "sqrt", "linear", or "log". Default: "sqrt".
  • disutilityType: Shape of \(D\): "quadratic" or "linear". Default: "quadratic".
  • Processing Window: Time window in milliseconds for historical data (when used with rolling updates).
  • Historical Count: Max records to process per update cycle.

Cold Start

Recommendations are always returned. With no history, RollingBehavior assigns platform random scores. With history, Sentimental Equilibrium ranks each offer from its response rate.

  • score() does not return \(x^*\) and \(c^*\). Those values come from computeEquilibrium() and are not written to arm_reward.
  • A live run uses the default \(r\), \(a\), and \(\rho\). RollingBehavior does not copy decayRate, effortEfficiency, or discountRate from the dynamic-engagement document onto the history row.
  • Live dynamic ranking: PostScoreSentimentalEquilibrium copies options-store arm_reward. It does not recompute the equilibrium.
  • Workbench Process job on the feature store: use Sentimental Equilibrium PreScored (PostScoreSentimentalEquilibriumPreScored) — a static extract of engagementScore / sentimental_equilibrium / … (same approach as spend personality). Do not use the dynamic post-score for that path.

When To Use

  • Ranking offers by a utility of the response rate
  • Prescribing how often to contact someone, via the Workbench sentimental equilibrium job

When NOT To Use

  • When arm_reward is expected to be the engagement rate. That rate is the Workbench batch, not this rank.

Example

from prediction.apis import deployment_management as dm from prediction.apis import online_learning_management as ol from prediction import jwt_access auth = jwt_access.Authenticate("http://localhost:3001/api", ecosystem_username, ecosystem_password) deployment_id = "demo-sentimental-equilibrium" online_learning_uuid = ol.create_online_learning( auth, algorithm="ecosystem_rewards", name=deployment_id, description="Sentimental Equilibrium configuration", feature_store_collection="set_up_features", feature_store_database="my_mongo_database", options_store_database="my_mongo_database", options_store_collection="demo-deployment_options", randomisation_processing_count=5000, randomisation_processing_window=604800000, contextual_variables_offer_key="offer", create_options_index=True, create_covering_index=True ) online_learning = dm.define_deployment_multi_armed_bandit(epsilon=0, dynamic_interaction_uuid=online_learning_uuid) parameter_access = dm.define_deployment_parameter_access( auth, lookup_key="customer_id", lookup_type="string", database="my_mongo_database", table_collection="customer_feature_store", datasource="mongodb" ) deployment_step = dm.create_deployment( auth, project_id="demo-project", deployment_id=deployment_id, description="Sentimental Equilibrium demo deployment", version="001", plugin_post_score_class="PlatformDynamicEngagement.java", plugin_pre_score_class="PreScoreDynamic.java", scoring_engine_path_dev="http://localhost:8091", mongo_connect=f"mongodb://{mongo_user}:{mongo_password}@localhost:54445/?authSource=admin", parameter_access=parameter_access, multi_armed_bandit=online_learning )

algorithm="ecosystem_rewards" on create_online_learning is the SDK document type. Scoring uses randomisation: "approach": "behaviorAlgos", "sub_approach": "sentimentalEquilibrium".

setData can read decayRate, effortEfficiency, discountRate, utilityType, and disutilityType from the first history row. A normal Dynamic Engagement call does not put those keys there, so scoring uses the defaults. The Workbench batch stores its own model settings on the sentimental-equilibrium config.

Last updated on