From 4b4638599fe1131af7dc423025e0889d4d23e1b6 Mon Sep 17 00:00:00 2001 From: meganrm Date: Mon, 10 Mar 2025 12:40:33 -0700 Subject: [PATCH 01/16] move equ check --- src/App.tsx | 26 +++++++++++- src/simulation/BindingSimulator2D.ts | 62 ---------------------------- src/utils/index.ts | 15 +++++++ 3 files changed, 39 insertions(+), 64 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 7ee59ba..443fb32 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,11 @@ -import { ReactNode, useCallback, useEffect, useMemo, useState } from "react"; +import { + ReactNode, + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from "react"; import { SimulariumController, TimeData, @@ -41,6 +48,7 @@ import { getColorIndex, indexToTime, insertValueSorted, + isSlopeZero, updateArrayInState, } from "./utils"; import PreComputedPlotData from "./simulation/PreComputedPlotData"; @@ -147,6 +155,20 @@ function App() { setDataColors([]); }, []); + const isPassedEquilibrium = useRef(false); + const arrayLength = currentProductConcentrationArray.length; + if ( + !isPassedEquilibrium.current && + arrayLength > 0 && + arrayLength % 50 === 0 + ) { + isPassedEquilibrium.current = isSlopeZero( + currentProductConcentrationArray + ); + } else if (arrayLength === 0 && isPassedEquilibrium.current) { + isPassedEquilibrium.current = false; + } + // SIMULATION INITIALIZATION const simulariumController = useMemo(() => { return new SimulariumController({}); @@ -523,7 +545,7 @@ function App() { return false; } - if (!clientSimulator.isMixed()) { + if (!isPassedEquilibrium.current) { setEquilibriumFeedbackTimeout("Not yet!"); return false; } diff --git a/src/simulation/BindingSimulator2D.ts b/src/simulation/BindingSimulator2D.ts index 9ecc057..f3cf5c3 100644 --- a/src/simulation/BindingSimulator2D.ts +++ b/src/simulation/BindingSimulator2D.ts @@ -34,7 +34,6 @@ export default class BindingSimulator implements IClientSimulatorImpl { numberAgentOnLeft: number = 0; numberAgentOnRight: number = 0; productColor: string = ""; - _isMixed: boolean = false; size: number; constructor( agents: InputAgent[], @@ -59,7 +58,6 @@ export default class BindingSimulator implements IClientSimulatorImpl { this.currentNumberOfUnbindingEvents = 0; this.system = new System(); this.instances = []; - this._isMixed = false; } private initializeAgents(agents: InputAgent[]): StoredAgent[] { @@ -102,59 +100,6 @@ export default class BindingSimulator implements IClientSimulatorImpl { return agents as StoredAgent[]; } - private countNumberOfInstancesOnEachSide(agentInstance: BindingInstance) { - if (this._isMixed) { - return; - } - if (agentInstance.id !== this.mixCheckAgent) { - return; - } - - // checking the rightmost third of the simulation - // and the left most third of the simulation - if (agentInstance.pos.x < -this.size / 3) { - this.numberAgentOnLeft++; - } else if (agentInstance.pos.x > this.size / 3) { - this.numberAgentOnRight++; - } - } - - private compareAgentsOnEachSide() { - // once the simulation is mixed, if it dips momentarily - // that's not a sign that equilibrium has been reversed - if (this._isMixed) { - return; - } - - // if either of the agents is a limiting reactant - // then the system is mixed when it's been used up - // even if the other agent is not evenly distributed - let limitReached = false; - this.agents.forEach((agent) => { - const agentUnbound = agent.count - this.currentNumberBound; - if ((agentUnbound / agent.count) * 100 < 10) { - limitReached = true; - return; - } - }); - if (limitReached) { - this._isMixed = true; - return; - } - - const diff = Math.abs(this.numberAgentOnLeft - this.numberAgentOnRight); - const total = this.numberAgentOnLeft + this.numberAgentOnRight; - const percentUnmixed = (diff / total) * 100; - if (percentUnmixed < 10) { - this._isMixed = true; - } - } - - private clearMixCounts() { - this.numberAgentOnLeft = 0; - this.numberAgentOnRight = 0; - } - private createBoundingLines() { const size = this.size; const points = [ @@ -287,10 +232,6 @@ export default class BindingSimulator implements IClientSimulatorImpl { /** PUBLIC METHODS BELOW */ - public isMixed() { - return this._isMixed; - } - public getEvents() { return { numberBindEvents: this.currentNumberOfBindingEvents, @@ -426,7 +367,6 @@ export default class BindingSimulator implements IClientSimulatorImpl { if (this.static || this.initialState) { return this.staticUpdate(); } - this.clearMixCounts(); for (let i = 0; i < this.instances.length; ++i) { const unbindingOccurred = this.instances[i].oneStep( @@ -437,13 +377,11 @@ export default class BindingSimulator implements IClientSimulatorImpl { this.currentNumberOfUnbindingEvents++; this.currentNumberBound--; } - this.countNumberOfInstancesOnEachSide(this.instances[i]); } // reset to zero for every tenth time point if (this.currentFrame % 10 === 0) { this.currentNumberOfBindingEvents = 0; this.currentNumberOfUnbindingEvents = 0; - this.compareAgentsOnEachSide(); } this.system.checkAll((response: Response) => { const { a, b, overlapV } = response; diff --git a/src/utils/index.ts b/src/utils/index.ts index b915608..0ebeddf 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -44,3 +44,18 @@ export const indexToTime = ( return index * timeFactor; } }; + +export const isSlopeZero = (array: number[]) => { + const sliceSize = 25; + const averageOfLastFive = + array.slice(-sliceSize).reduce((a, b) => a + b) / sliceSize; + const averageOfFirstFive = + array.slice(-sliceSize * 2, -sliceSize).reduce((a, b) => a + b) / + sliceSize; + const slope = averageOfLastFive - averageOfFirstFive; + if (Math.abs(slope) < 0.01) { + return true; + } else { + return false; + } +}; From 7798a026e87d68c0bcba03d3e8acbbf50c608193 Mon Sep 17 00:00:00 2001 From: meganrm Date: Mon, 10 Mar 2025 12:49:31 -0700 Subject: [PATCH 02/16] change values for low affinity --- src/simulation/LiveSimulationData.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/simulation/LiveSimulationData.ts b/src/simulation/LiveSimulationData.ts index 60622b4..0ee9655 100644 --- a/src/simulation/LiveSimulationData.ts +++ b/src/simulation/LiveSimulationData.ts @@ -41,16 +41,16 @@ const agentC: InputAgent = { id: 2, name: AgentName.C, initialConcentration: 0, - radius: 1, + radius: 0.4, partners: [0], - kOn: 0.5, - kOff: 0.8, + kOn: 0.3, + kOff: 0.9, color: AGENT_C_COLOR, }; const kds = { [Module.A_B_AB]: 0.75, - [Module.A_C_AC]: 10, + [Module.A_C_AC]: 74, [Module.A_B_C_AB_AC]: 5, }; @@ -77,7 +77,7 @@ export default class LiveSimulation implements ISimulationData { static INITIAL_CONCENTRATIONS = { [AgentName.A]: 10, [AgentName.B]: 4, - [AgentName.C]: 10, + [AgentName.C]: 40, }; PRODUCT = { [Module.A_B_AB]: ProductName.AB, @@ -112,7 +112,7 @@ export default class LiveSimulation implements ISimulationData { maxConcentration = 10; break; case Module.A_C_AC: - maxConcentration = 20; //TODO: adjust these as needed + maxConcentration = 100; break; case Module.A_B_C_AB_AC: maxConcentration = 20; //TODO: adjust these as needed From 06490093740af23af5cfff049d36a7c2583b1c33 Mon Sep 17 00:00:00 2001 From: meganrm Date: Tue, 25 Mar 2025 13:44:47 -0700 Subject: [PATCH 03/16] adjust numbers --- src/simulation/LiveSimulationData.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/simulation/LiveSimulationData.ts b/src/simulation/LiveSimulationData.ts index 0ee9655..ad780d6 100644 --- a/src/simulation/LiveSimulationData.ts +++ b/src/simulation/LiveSimulationData.ts @@ -77,7 +77,7 @@ export default class LiveSimulation implements ISimulationData { static INITIAL_CONCENTRATIONS = { [AgentName.A]: 10, [AgentName.B]: 4, - [AgentName.C]: 40, + [AgentName.C]: 30, }; PRODUCT = { [Module.A_B_AB]: ProductName.AB, @@ -112,7 +112,7 @@ export default class LiveSimulation implements ISimulationData { maxConcentration = 10; break; case Module.A_C_AC: - maxConcentration = 100; + maxConcentration = 75; break; case Module.A_B_C_AB_AC: maxConcentration = 20; //TODO: adjust these as needed From 662c7e942bb7c253a308296f26be8973dae30154 Mon Sep 17 00:00:00 2001 From: meganrm Date: Tue, 25 Mar 2025 13:45:09 -0700 Subject: [PATCH 04/16] change scale for each module --- src/components/ScaleBar.tsx | 3 ++- .../concentration-display/ConcentrationSlider.tsx | 15 +++++++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/components/ScaleBar.tsx b/src/components/ScaleBar.tsx index 9669e18..7300644 100644 --- a/src/components/ScaleBar.tsx +++ b/src/components/ScaleBar.tsx @@ -11,7 +11,8 @@ interface ScaleBarProps { const ScaleBar: React.FC = ({ productColor }) => { const { maxConcentration } = useContext(SimulariumContext); const labelArray = []; - for (let i = maxConcentration; i >= 0; i = i - 2) { + const interval = maxConcentration / 5; + for (let i = maxConcentration; i >= 0; i = i - interval) { labelArray.push(i); } return ( diff --git a/src/components/concentration-display/ConcentrationSlider.tsx b/src/components/concentration-display/ConcentrationSlider.tsx index c82296d..f1efb53 100644 --- a/src/components/concentration-display/ConcentrationSlider.tsx +++ b/src/components/concentration-display/ConcentrationSlider.tsx @@ -62,18 +62,21 @@ const ConcentrationSlider: React.FC = ({ }) => { // eslint-disable-next-line react-hooks/exhaustive-deps const disabledNumbers = [0]; - + const stepSize = useRef(0); const marks = useMemo(() => { + stepSize.current = (max - min) / 5; const marks: SliderSingleProps["marks"] = {}; - for (let index = min; index <= max; index = index + 2) { + for (let index = min; index <= max; index = index + stepSize.current) { marks[index] = { label: ( - onChangeComplete && onChangeComplete(name, index) - } + onMouseUp={() => { + if (onChangeComplete) { + onChangeComplete(name, index); + } + }} /> ), }; @@ -87,7 +90,7 @@ const ConcentrationSlider: React.FC = ({ name={name} min={min} max={max} - step={2} + step={stepSize.current} onChange={onChange} onChangeComplete={onChangeComplete} marks={marks} From c29a3af7bd7e145ec0fd34d8ccfb228f0d23c93b Mon Sep 17 00:00:00 2001 From: meganrm Date: Tue, 25 Mar 2025 13:45:41 -0700 Subject: [PATCH 05/16] make response module specific --- src/components/quiz-questions/KdQuestion.tsx | 51 +++++++++++++++----- 1 file changed, 39 insertions(+), 12 deletions(-) diff --git a/src/components/quiz-questions/KdQuestion.tsx b/src/components/quiz-questions/KdQuestion.tsx index a8773cf..bc46fe9 100644 --- a/src/components/quiz-questions/KdQuestion.tsx +++ b/src/components/quiz-questions/KdQuestion.tsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React, { useContext, useEffect, useState } from "react"; import { valueType } from "antd/es/statistic/utils"; import { Flex } from "antd"; @@ -8,6 +8,7 @@ import InputNumber from "../shared/InputNumber"; import { FormState } from "./types"; import styles from "./popup.module.css"; import { MICRO } from "../../constants"; +import { SimulariumContext } from "../../simulation/context"; interface KdQuestionProps { kd: number; @@ -18,6 +19,42 @@ const KdQuestion: React.FC = ({ kd, canAnswer }) => { const [selectedAnswer, setSelectedAnswer] = useState(null); const [formState, setFormState] = useState(FormState.Clear); + const { module } = useContext(SimulariumContext); + + useEffect(() => { + console.log("module change", module); + setSelectedAnswer(null); + setFormState(FormState.Clear); + }, [module]); + + const getSuccessMessage = (selectedAnswer: number) => { + if (selectedAnswer < 5) { + return ( + <> + {selectedAnswer} {MICRO}M is considered a{" "} + + low Kd + + , which means A and B have a high affinity{" "} + for one another because it takes a low amount of B to create + the complex. + + ); + } else { + return ( + <> + {selectedAnswer} {MICRO}M is considered a{" "} + + high Kd + + , which means A and C have a low affinity{" "} + for one another because it takes a lot of C to create the + complex. + + ); + } + }; + const handleAnswerSelection = (answer: valueType | null) => { setSelectedAnswer(Number(answer)); @@ -77,17 +114,7 @@ const KdQuestion: React.FC = ({ kd, canAnswer }) => { title="What is the binding affinity?" formContent={formContent} onSubmit={handleSubmit} - successMessage={ - <> - {selectedAnswer} {MICRO}M is considered a{" "} - - low Kd - - , which means A and B have a{" "} - high affinity for one another because - it takes a low amount of B to create the complex. - - } + successMessage={getSuccessMessage(selectedAnswer!)} failureMessage="Visit the “Learn how to derive Kd” button above, then use the Equilibrium concentration plot to answer." formState={formState} id="Kd Value" From 259648b8387b0784839af046173013d7ed3e7b1d Mon Sep 17 00:00:00 2001 From: meganrm Date: Tue, 25 Mar 2025 13:45:53 -0700 Subject: [PATCH 06/16] adjust axis --- src/components/plots/EquilibriumPlot.tsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/components/plots/EquilibriumPlot.tsx b/src/components/plots/EquilibriumPlot.tsx index fab1427..6357227 100644 --- a/src/components/plots/EquilibriumPlot.tsx +++ b/src/components/plots/EquilibriumPlot.tsx @@ -37,7 +37,8 @@ const EquilibriumPlot: React.FC = ({ getAgentColor, adjustableAgentName, } = useContext(SimulariumContext); - + const xMax = Math.max(...x); + const xAxisMax = Math.max(kd * 2, xMax * 1.1); const hintOverlay = (
= ({ }; const horizontalLine = { - x: [0, kd * 2], + x: [0, xAxisMax], y: [5, 5], mode: "lines", name: "50% bound", @@ -74,7 +75,7 @@ const EquilibriumPlot: React.FC = ({ line: lineOptions, }; const horizontalLineMax = { - x: [0, kd * 2], + x: [0, xAxisMax], y: [10, 10], mode: "lines", name: "Initial [A]", @@ -108,7 +109,7 @@ const EquilibriumPlot: React.FC = ({ height: Math.max(130, height), xaxis: { ...AXIS_SETTINGS, - range: [0, kd * 2], + range: [0, xAxisMax], title: `[${adjustableAgentName}] ${MICRO}M`, titlefont: { ...AXIS_SETTINGS.titlefont, From 2701ae8b58e21aae62b95f27fc8caa4e37d000f9 Mon Sep 17 00:00:00 2001 From: meganrm Date: Tue, 25 Mar 2025 13:46:29 -0700 Subject: [PATCH 07/16] use input number for module change --- src/components/AdminUi.tsx | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/components/AdminUi.tsx b/src/components/AdminUi.tsx index a94c959..66e287e 100644 --- a/src/components/AdminUi.tsx +++ b/src/components/AdminUi.tsx @@ -3,8 +3,9 @@ import React, { useContext, useEffect } from "react"; import Slider from "./shared/Slider"; import { BG_DARK, LIGHT_GREY } from "../constants/colors"; import { SimulariumContext } from "../simulation/context"; -import { SliderSingleProps } from "antd"; +import { InputNumber, SliderSingleProps } from "antd"; import { zStacking } from "../constants/z-stacking"; +import { Module } from "../types"; interface AdminUIProps { totalPages: number; @@ -72,18 +73,15 @@ const AdminUI: React.FC = ({ name="time factor (ns)" />

Module number

- { - setModule(value); + value={Number(module)} + onChange={(value): void => { + setModule(value as Module); }} - overrideValue={module} - marks={moduleMarks} disabled={false} - name="time factor (ns)" />
@@ -94,6 +92,7 @@ const AdminUI: React.FC = ({ max={100} step={1} initialValue={timeFactor} + overrideValue={timeFactor} onChange={(_, value) => { setTimeFactor(value); }} From edaaa717f6fbe474db3928b4a2c61238049a0d4e Mon Sep 17 00:00:00 2001 From: meganrm Date: Tue, 25 Mar 2025 13:46:48 -0700 Subject: [PATCH 08/16] make slope check a little looser --- src/utils/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/index.ts b/src/utils/index.ts index 0ebeddf..0d80674 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -53,7 +53,7 @@ export const isSlopeZero = (array: number[]) => { array.slice(-sliceSize * 2, -sliceSize).reduce((a, b) => a + b) / sliceSize; const slope = averageOfLastFive - averageOfFirstFive; - if (Math.abs(slope) < 0.01) { + if (Math.abs(slope) < 0.05) { return true; } else { return false; From 8cc356c76b1dc86a234671cda6666d2e764643b6 Mon Sep 17 00:00:00 2001 From: meganrm Date: Tue, 25 Mar 2025 13:50:02 -0700 Subject: [PATCH 09/16] simply collision checks --- src/simulation/BindingInstance.ts | 10 +- src/simulation/BindingSimulator2D.ts | 166 +++++++++------------------ 2 files changed, 59 insertions(+), 117 deletions(-) diff --git a/src/simulation/BindingInstance.ts b/src/simulation/BindingInstance.ts index 8d60e64..8435f1d 100644 --- a/src/simulation/BindingInstance.ts +++ b/src/simulation/BindingInstance.ts @@ -1,5 +1,7 @@ -import { Circle, Vector } from "detect-collisions"; +import { Circle } from "detect-collisions"; import { random } from "lodash"; +import { Vector } from "sat"; + import LiveSimulationData from "./LiveSimulationData"; class BindingInstance extends Circle { @@ -48,7 +50,6 @@ class BindingInstance extends Circle { } private releaseFromParent() { - this.isTrigger = false; this.bound = false; this.parent = null; } @@ -57,7 +58,6 @@ class BindingInstance extends Circle { parent: BindingInstance, overlapV: Vector ): BindingInstance { - this.isTrigger = true; this.parent = parent; // adjust the ligand to the exact edge of the parent this.moveInstance(-overlapV.x, -overlapV.y); @@ -68,6 +68,8 @@ class BindingInstance extends Circle { /** PUBLIC METHODS BELOW */ public moveInstance(x: number, y: number) { + // if we're trying to move a bound instance, move the parent instead + // and then we'll resolve the child position if (this.parent) { this.parent.moveInstance(x, y); } else { @@ -161,7 +163,6 @@ class BindingInstance extends Circle { return false; } this.child = null; - this.isTrigger = false; ligand.releaseFromParent(); // QUESTION: should the ligand be moved to a random position? return true; @@ -180,7 +181,6 @@ class BindingInstance extends Circle { if (!willBind) { return false; } - this.isTrigger = true; this.child = ligand.bindToParent(this, overlapV); return true; } diff --git a/src/simulation/BindingSimulator2D.ts b/src/simulation/BindingSimulator2D.ts index f3cf5c3..42e38d1 100644 --- a/src/simulation/BindingSimulator2D.ts +++ b/src/simulation/BindingSimulator2D.ts @@ -169,59 +169,6 @@ export default class BindingSimulator implements IClientSimulatorImpl { } } - private resolveCollision( - a: BindingInstance, - b: BindingInstance, - overlapV: Vector, - numberOfPasses: number = 0 - ) { - let toCheck = null; - const { x, y } = overlapV; - // if neither is a trigger, then they - // will both get moved by system.separate() - if (!a.isTrigger && !b.isTrigger) { - return; - } - if (numberOfPasses > 8) { - return; - } - - // prefer to move an instance that is not bound, ie, not isTrigger - // because after it's moved any additional overlaps will be resolved by the system - if (!a.isTrigger) { - a.moveInstance(-x, -y); - toCheck = a; - } else if (!b.isTrigger && b.type === "Circle") { - b.moveInstance(x, y); - toCheck = b; - } else { - a.moveInstance(-x, -y); - toCheck = a; - } - if (toCheck) { - numberOfPasses++; - // after moving the instance, check if it overlaps with any other instance - this.system.checkOne(toCheck, (response: Response) => { - if (response) { - const { a, b, overlapV, overlap } = response; - if (!a.isBoundPair(b)) { - // This check is to keep from having to resolve - // a ridiculous number of collisions - // the value is half the radius of the larger agent - if (overlap > 1) { - return this.resolveCollision( - a, - b, - overlapV, - numberOfPasses - ); - } - } - } - }); - } - } - private relax(maxCycles: number = 30) { let cycles = 0; while (cycles < maxCycles) { @@ -320,11 +267,7 @@ export default class BindingSimulator implements IClientSimulatorImpl { return concentrations; } - public staticUpdate() { - // update the number of agents without - // changing their positions - this.relax(); - + private getAgentData() { const agentData: number[] = []; for (let ii = 0; ii < this.instances.length; ++ii) { const instance = this.instances[ii]; @@ -344,30 +287,10 @@ export default class BindingSimulator implements IClientSimulatorImpl { agentData.push(instance.r); // collision radius agentData.push(0); // subpoints } - const frameData: VisDataMessage = { - // TODO get msgType out of here - msgType: ClientMessageEnum.ID_VIS_DATA_ARRIVE, - bundleStart: this.currentFrame, - bundleSize: 1, // frames - bundleData: [ - { - data: agentData, - frameNumber: this.currentFrame, - time: this.currentFrame, - }, - ], - fileName: "hello world", - }; - this.static = false; - this.currentFrame++; - return frameData; + return agentData; } - public update(): VisDataMessage { - if (this.static || this.initialState) { - return this.staticUpdate(); - } - + private updateAgents() { for (let i = 0; i < this.instances.length; ++i) { const unbindingOccurred = this.instances[i].oneStep( this.size, @@ -378,11 +301,36 @@ export default class BindingSimulator implements IClientSimulatorImpl { this.currentNumberBound--; } } - // reset to zero for every tenth time point - if (this.currentFrame % 10 === 0) { - this.currentNumberOfBindingEvents = 0; - this.currentNumberOfUnbindingEvents = 0; + } + + private resolveChildPositions() { + for (let i = 0; i < this.instances.length; ++i) { + const instance = this.instances[i]; + if (instance.parent) { + const bindingOverlap = instance.r * 0.5; + const parentPosition = instance.parent.pos; + const childPosition = instance.pos; + const distanceVector = new Vector( + childPosition.x - parentPosition.x, + childPosition.y - parentPosition.y + ); + const distance = Math.sqrt( + distanceVector.x ** 2 + distanceVector.y ** 2 + ); + const perfectBoundDistance = + instance.parent.r + instance.r - bindingOverlap; + const tolerance = 0.1; + if (Math.abs(distance - perfectBoundDistance) > tolerance) { + const ratio = perfectBoundDistance / distance; + const x = parentPosition.x + distanceVector.x * ratio; + const y = parentPosition.y + distanceVector.y * ratio; + instance.setPosition(x, y); + } + } } + } + + private resolveBindingReactions() { this.system.checkAll((response: Response) => { const { a, b, overlapV } = response; @@ -415,39 +363,33 @@ export default class BindingSimulator implements IClientSimulatorImpl { this.currentNumberBound++; } } - // Now that binding has been resolved, resolve collisions - // if they are not bound the system will resolve the collision - if (!a.isBoundPair(b)) { - if (!a.isStatic && !b.isStatic) { - this.resolveCollision(a, b, overlapV); - } - } } else { console.log("no response"); } }); - this.relax(5); - // fill agent data. - const agentData: number[] = []; + } - for (let ii = 0; ii < this.instances.length; ++ii) { - const instance = this.instances[ii]; - agentData.push(VisTypes.ID_VIS_TYPE_DEFAULT); // vis type - agentData.push(ii); // instance id - agentData.push( - instance.bound || instance.child - ? 100 + instance.id - : instance.id - ); // type - agentData.push(instance.pos.x); // x - agentData.push(instance.pos.y); // y - agentData.push(0); // z - agentData.push(0); // rx - agentData.push(0); // ry - agentData.push(0); // rz - agentData.push(instance.r); // collision radius - agentData.push(0); // subpoints + public update(): VisDataMessage { + let agentData: number[] = []; + if (this.static || this.initialState) { + // update number of agents + // without changing positions + this.relax(); + agentData = this.getAgentData(); + this.static = false; + } else { + this.updateAgents(); + // reset to zero for every tenth time point + if (this.currentFrame % 10 === 0) { + this.currentNumberOfBindingEvents = 0; + this.currentNumberOfUnbindingEvents = 0; + } + this.resolveBindingReactions(); + this.relax(3); + this.resolveChildPositions(); + agentData = this.getAgentData(); } + const frameData: VisDataMessage = { // TODO get msgType out of here msgType: ClientMessageEnum.ID_VIS_DATA_ARRIVE, @@ -513,7 +455,7 @@ export default class BindingSimulator implements IClientSimulatorImpl { position: { x: 0, y: 0, - z: 65, + z: 70, }, }, typeMapping: typeMapping, From 5d4ba32cf34f62fe68c723db901fdae49755d844 Mon Sep 17 00:00:00 2001 From: meganrm Date: Tue, 25 Mar 2025 13:50:49 -0700 Subject: [PATCH 10/16] keep from doing muliple set state calls --- src/App.tsx | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 443fb32..021d5c3 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -275,6 +275,7 @@ function App() { uniqMeasuredConcentrations.length >= 3 ); }, [hasAValueAboveKd, hasAValueBelowKd, uniqMeasuredConcentrations]); + const handleNewInputConcentration = useCallback( (name: string, value: number) => { if (value === 0) { @@ -292,10 +293,10 @@ function App() { name as keyof typeof LiveSimulationData.AVAILABLE_AGENTS; const agentId = LiveSimulationData.AVAILABLE_AGENTS[agentName].id; clientSimulator.changeConcentration(agentId, value); - simulariumController.gotoTime(time + 1); + simulariumController.gotoTime(1); // the number isn't used, but it triggers the update resetCurrentRunAnalysisState(); }, - [clientSimulator, time, simulariumController] + [clientSimulator, simulariumController] ); const totalReset = useCallback(() => { setLiveConcentration({ @@ -348,6 +349,7 @@ function App() { usePageNumber( page, (page) => + currentModule === 1 && page === PROMPT_TO_ADJUST_B && isPlaying && recordedInputConcentration.length > 0 && @@ -417,12 +419,18 @@ function App() { useEffect(() => { const { section } = content[currentModule][page]; - if (section === Section.Experiment) { + if ( + section === Section.Experiment && + timeFactor !== LiveSimulationData.DEFAULT_TIME_FACTOR + ) { setTimeFactor(LiveSimulationData.DEFAULT_TIME_FACTOR); - } else if (section === Section.Introduction) { + } else if ( + section === Section.Introduction && + timeFactor !== LiveSimulationData.INITIAL_TIME_FACTOR + ) { setTimeFactor(LiveSimulationData.INITIAL_TIME_FACTOR); } - }, [currentModule, page]); + }, [currentModule, page, timeFactor]); const addProductionTrace = (previousConcentration: number) => { const traces = productOverTimeTraces; @@ -447,7 +455,6 @@ function App() { const handleStartExperiment = () => { simulariumController.pause(); totalReset(); - setTimeFactor(LiveSimulationData.DEFAULT_TIME_FACTOR); setPage(page + 1); }; From 1afdc203398fcd79289a6da58b8085d5ea2c6a71 Mon Sep 17 00:00:00 2001 From: meganrm Date: Tue, 25 Mar 2025 13:54:22 -0700 Subject: [PATCH 11/16] remove log --- src/components/quiz-questions/KdQuestion.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/quiz-questions/KdQuestion.tsx b/src/components/quiz-questions/KdQuestion.tsx index bc46fe9..1970336 100644 --- a/src/components/quiz-questions/KdQuestion.tsx +++ b/src/components/quiz-questions/KdQuestion.tsx @@ -22,7 +22,6 @@ const KdQuestion: React.FC = ({ kd, canAnswer }) => { const { module } = useContext(SimulariumContext); useEffect(() => { - console.log("module change", module); setSelectedAnswer(null); setFormState(FormState.Clear); }, [module]); From a714d6cbc1831a7836a0b72fd18237e90949f8a7 Mon Sep 17 00:00:00 2001 From: meganrm Date: Thu, 27 Mar 2025 10:34:26 -0700 Subject: [PATCH 12/16] make clearer function name --- src/simulation/BindingSimulator2D.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/simulation/BindingSimulator2D.ts b/src/simulation/BindingSimulator2D.ts index 42e38d1..36b8d3e 100644 --- a/src/simulation/BindingSimulator2D.ts +++ b/src/simulation/BindingSimulator2D.ts @@ -290,7 +290,7 @@ export default class BindingSimulator implements IClientSimulatorImpl { return agentData; } - private updateAgents() { + private updateAgentsPositions() { for (let i = 0; i < this.instances.length; ++i) { const unbindingOccurred = this.instances[i].oneStep( this.size, @@ -306,6 +306,8 @@ export default class BindingSimulator implements IClientSimulatorImpl { private resolveChildPositions() { for (let i = 0; i < this.instances.length; ++i) { const instance = this.instances[i]; + // if the instance is a child, we're going to move it to be + // perfectly bound to its parent (with a slight overlap) if (instance.parent) { const bindingOverlap = instance.r * 0.5; const parentPosition = instance.parent.pos; @@ -378,7 +380,7 @@ export default class BindingSimulator implements IClientSimulatorImpl { agentData = this.getAgentData(); this.static = false; } else { - this.updateAgents(); + this.updateAgentsPositions(); // reset to zero for every tenth time point if (this.currentFrame % 10 === 0) { this.currentNumberOfBindingEvents = 0; From 88965e2725c46c9036435b9e73e23bd33841e053 Mon Sep 17 00:00:00 2001 From: meganrm Date: Thu, 27 Mar 2025 10:50:21 -0700 Subject: [PATCH 13/16] simplify useEffect --- src/App.tsx | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 021d5c3..a1a1edc 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -417,20 +417,14 @@ function App() { switchToLiveSimulation, ]); + const { section } = content[currentModule][page]; useEffect(() => { - const { section } = content[currentModule][page]; - if ( - section === Section.Experiment && - timeFactor !== LiveSimulationData.DEFAULT_TIME_FACTOR - ) { + if (section === Section.Experiment) { setTimeFactor(LiveSimulationData.DEFAULT_TIME_FACTOR); - } else if ( - section === Section.Introduction && - timeFactor !== LiveSimulationData.INITIAL_TIME_FACTOR - ) { + } else if (section === Section.Introduction) { setTimeFactor(LiveSimulationData.INITIAL_TIME_FACTOR); } - }, [currentModule, page, timeFactor]); + }, [section]); const addProductionTrace = (previousConcentration: number) => { const traces = productOverTimeTraces; From d3e750c8b326afc47a161be54fb70b3042b044d4 Mon Sep 17 00:00:00 2001 From: Megan Riel-Mehan Date: Thu, 3 Apr 2025 12:47:54 -0700 Subject: [PATCH 14/16] Update src/components/concentration-display/ConcentrationSlider.tsx Co-authored-by: Cameron Fraser <53030214+frasercl@users.noreply.github.com> --- .../concentration-display/ConcentrationSlider.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/components/concentration-display/ConcentrationSlider.tsx b/src/components/concentration-display/ConcentrationSlider.tsx index f1efb53..fbc5160 100644 --- a/src/components/concentration-display/ConcentrationSlider.tsx +++ b/src/components/concentration-display/ConcentrationSlider.tsx @@ -72,11 +72,7 @@ const ConcentrationSlider: React.FC = ({ { - if (onChangeComplete) { - onChangeComplete(name, index); - } - }} + onMouseUp={() => onChangeComplete?.(name, index)} /> ), }; From bee43911d1d19cfe76b5c20dd4bc07f545b7583d Mon Sep 17 00:00:00 2001 From: Megan Riel-Mehan Date: Thu, 3 Apr 2025 12:48:05 -0700 Subject: [PATCH 15/16] Update src/App.tsx Co-authored-by: Cameron Fraser <53030214+frasercl@users.noreply.github.com> --- src/App.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/App.tsx b/src/App.tsx index a1a1edc..0417a8d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -349,7 +349,7 @@ function App() { usePageNumber( page, (page) => - currentModule === 1 && + currentModule === Module.A_B_AB && page === PROMPT_TO_ADJUST_B && isPlaying && recordedInputConcentration.length > 0 && From 2d145dbc1598d4b1d2cf6e325f8d3d00cf3a039a Mon Sep 17 00:00:00 2001 From: meganrm Date: Thu, 3 Apr 2025 12:52:50 -0700 Subject: [PATCH 16/16] useMemo for stepsize --- .../concentration-display/ConcentrationSlider.tsx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/components/concentration-display/ConcentrationSlider.tsx b/src/components/concentration-display/ConcentrationSlider.tsx index fbc5160..f978d23 100644 --- a/src/components/concentration-display/ConcentrationSlider.tsx +++ b/src/components/concentration-display/ConcentrationSlider.tsx @@ -62,11 +62,10 @@ const ConcentrationSlider: React.FC = ({ }) => { // eslint-disable-next-line react-hooks/exhaustive-deps const disabledNumbers = [0]; - const stepSize = useRef(0); + const stepSize = useMemo(() => (max - min) / 5, [min, max]); const marks = useMemo(() => { - stepSize.current = (max - min) / 5; const marks: SliderSingleProps["marks"] = {}; - for (let index = min; index <= max; index = index + stepSize.current) { + for (let index = min; index <= max; index = index + stepSize) { marks[index] = { label: ( = ({ }; } return marks; - }, [min, max, disabledNumbers, onChangeComplete, name]); + }, [min, max, disabledNumbers, onChangeComplete, name, stepSize]); return ( = ({ name={name} min={min} max={max} - step={stepSize.current} + step={stepSize} onChange={onChange} onChangeComplete={onChangeComplete} marks={marks}