Skip to content

Commit

Permalink
invertible and symbolic line segment length
Browse files Browse the repository at this point in the history
  • Loading branch information
dqnykamp authored and jaltekruse committed Feb 28, 2023
1 parent c2e1815 commit ec906e8
Show file tree
Hide file tree
Showing 3 changed files with 194 additions and 31 deletions.
64 changes: 60 additions & 4 deletions cypress/e2e/DoenetML/tagSpecific/linesegment.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -3362,6 +3362,10 @@ describe('LineSegment Tag Tests', function () {
<linesegment name="l" endpoints="$A $B" />
</graph>
<copy prop="length" target="l" assignNames="length" />
<point name="Ap" copySource="A" />
<point name="Bp" copySource="B" />
<mathinput name="milength" bindValueTo="$length" />
<p><booleaninput name="bi"/><boolean copySource="bi" name="bi2" /></p>
`}, "*");
});

Expand All @@ -3372,7 +3376,9 @@ describe('LineSegment Tag Tests', function () {
let t2x = 7, t2y = -2;
let len = Math.sqrt((t1y - t2y) ** 2 + (t1x - t2x) ** 2);

cy.get('#\\/length').should('contain.text', String(Math.round(len*10**9)/10**9))
cy.get('#\\/length').should('contain.text', String(Math.round(len * 10 ** 9) / 10 ** 9))
cy.get('#\\/Ap .mjx-mrow').eq(0).should('have.text', '(3,4)');
cy.get('#\\/Bp .mjx-mrow').eq(0).should('have.text', '(7,−2)');

cy.window().then(async (win) => {
let stateVariables = await win.returnAllStateVariables1();
Expand All @@ -3391,17 +3397,47 @@ describe('LineSegment Tag Tests', function () {
})

cy.get('#\\/length').should('contain.text', String(len))
cy.get('#\\/Ap .mjx-mrow').eq(0).should('have.text', '(7,3)');
cy.get('#\\/Bp .mjx-mrow').eq(0).should('have.text', '(7,−2)');

})

cy.window().then(async (win) => {
let stateVariables = await win.returnAllStateVariables1();
expect(Math.abs(stateVariables['/l'].stateValues.length)).eq(len);
expect(stateVariables['/l'].stateValues.length).eq(len);
})


cy.get('#\\/milength textarea').type("{end}{backspace}10{enter}", { force: true });

cy.get('#\\/length').should('contain.text', '10')
cy.get('#\\/Ap .mjx-mrow').eq(0).should('have.text', '(7,5.5)');
cy.get('#\\/Bp .mjx-mrow').eq(0).should('have.text', '(7,−4.5)');

cy.window().then(async (win) => {
let stateVariables = await win.returnAllStateVariables1();
expect(stateVariables['/l'].stateValues.length).eq(10);
})


cy.log("ignore requested negative length");
cy.get('#\\/milength textarea').type("{end}{backspace}{backspace}-3{enter}", { force: true });
cy.get("#\\/bi").click();
cy.get('#\\/bi2').should('have.text', 'true'); // so know that core has responded to both requests

cy.get('#\\/length').should('contain.text', '10')
cy.get('#\\/Ap .mjx-mrow').eq(0).should('have.text', '(7,5.5)');
cy.get('#\\/Bp .mjx-mrow').eq(0).should('have.text', '(7,−4.5)');

cy.window().then(async (win) => {
let stateVariables = await win.returnAllStateVariables1();
expect(stateVariables['/l'].stateValues.length).eq(10);
})


cy.window().then(async (win) => {

t1y = 5.5;
t2x = -9;
t2y = 5;
len = Math.sqrt((t1y - t2y) ** 2 + (t1x - t2x) ** 2);
Expand All @@ -3411,8 +3447,9 @@ describe('LineSegment Tag Tests', function () {
args: { x: t2x, y: t2y }
})

cy.get('#\\/length').should('contain.text', String(Math.round(len*10**8)/10**8))

cy.get('#\\/length').should('contain.text', String(Math.round(len * 10 ** 8) / 10 ** 8))
cy.get('#\\/Ap .mjx-mrow').eq(0).should('have.text', '(7,5.5)');
cy.get('#\\/Bp .mjx-mrow').eq(0).should('have.text', '(−9,5)');
})

cy.window().then(async (win) => {
Expand All @@ -3421,6 +3458,25 @@ describe('LineSegment Tag Tests', function () {
})
});

it('lineSegment symbolic length', () => {
cy.window().then(async (win) => {
win.postMessage({
doenetML: `
<text>a</text>
<linesegment name="l" endpoints="(x,y) (u,v)" />
<copy prop="length" source="l" assignNames="length" />
`}, "*");
});

cy.get('#\\/_text1').should('have.text', 'a')// to wait for page to load


cy.window().then(async (win) => {
let stateVariables = await win.returnAllStateVariables1();
expect(me.fromAst(stateVariables['/l'].stateValues.length).equals(me.fromText('sqrt((x-u)^2+(y-v)^2)'))).eq(true);
})
});

it('label positioning', () => {
cy.window().then(async (win) => {
win.postMessage({
Expand Down
24 changes: 23 additions & 1 deletion src/Core/Core.js
Original file line number Diff line number Diff line change
Expand Up @@ -9340,7 +9340,29 @@ export default class Core {
}
}
} else {
Object.assign(arrayInstructionInProgress.desiredValue, newInstruction.desiredValue);
if (depStateVarObj.nDimensions === 1) {
Object.assign(arrayInstructionInProgress.desiredValue, newInstruction.desiredValue);
} else {
// need to convert multidimensional array (newInstruction.desiredValue)
// to an object with multidimesional arrayKeys
// where each array key is a concatenation of the array indices, joined by commas

let convert_md_array = (array, n_dim) => {
if (n_dim === 1) {
return Object.assign({}, array)
} else {
let new_obj = {}
for (let ind in array) {
let sub_obj = convert_md_array(array[ind], n_dim - 1);
for (let key in sub_obj) {
new_obj[`${ind},${key}`] = sub_obj[key]
}
}
return new_obj;
}
}
Object.assign(arrayInstructionInProgress.desiredValue, convert_md_array(newInstruction.desiredValue, depStateVarObj.nDimensions))
}
}


Expand Down
137 changes: 111 additions & 26 deletions src/Core/components/LineSegment.js
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,117 @@ export default class LineSegment extends GraphicalComponent {
}
}

stateVariableDefinitions.length = {
public: true,
shadowingInstructions: {
createComponentOfType: "math",
},
returnDependencies: () => ({
nDimensions: {
dependencyType: "stateVariable",
variableName: "nDimensions",
},
endpoints: {
dependencyType: "stateVariable",
variableName: "endpoints"
}
}),
definition({ dependencyValues }) {
let length2 = 0;
let epoint1 = dependencyValues.endpoints[0];
let epoint2 = dependencyValues.endpoints[1];
let all_numeric = true;
for (let dim = 0; dim < dependencyValues.nDimensions; dim++) {
let v1 = epoint1[dim].evaluate_to_constant();
if (!Number.isFinite(v1)) {
all_numeric = false;
break;
}
let v2 = epoint2[dim].evaluate_to_constant();
if (!Number.isFinite(v2)) {
all_numeric = false;
break;
}
let d = v1 - v2;
length2 += d * d;
}

if (all_numeric) {
return { setValue: { length: me.fromAst(Math.sqrt(length2)) } };
}

length2 = ['+'];
for (let dim = 0; dim < dependencyValues.nDimensions; dim++) {
length2.push([
'^',
['+', epoint1[dim], ['-', epoint2[dim]]],
2
])
}

return {
setValue: {
length:
me.fromAst(['apply', 'sqrt', length2])
}
}

},
inverseDefinition({ desiredStateVariableValues, dependencyValues }) {
let midpoint = [];
let dir = [];
let epoint1 = dependencyValues.endpoints[0];
let epoint2 = dependencyValues.endpoints[1];
let all_numeric = true;
for (let dim = 0; dim < dependencyValues.nDimensions; dim++) {
let v1 = epoint1[dim].evaluate_to_constant();
if (!Number.isFinite(v1)) {
all_numeric = false;
break;
}
let v2 = epoint2[dim].evaluate_to_constant();
if (!Number.isFinite(v2)) {
all_numeric = false;
break;
}
midpoint.push((v1 + v2) / 2)
dir.push(v1 - v2);
}

if (!all_numeric) {
return { success: false }
}

// make dir be unit length
let dir_length = Math.sqrt(dir.reduce((a, c) => a + c * c, 0));
dir = dir.map(x => x / dir_length);

let desiredLength = desiredStateVariableValues.length.evaluate_to_constant();

if (!Number.isFinite(desiredLength) || desiredLength < 0) {
return { success: false }
}

let desiredEndpoint1 = [], desiredEndpoint2 = [];
let halfDesiredlength = desiredLength / 2;

for (let dim = 0; dim < dependencyValues.nDimensions; dim++) {
desiredEndpoint1.push(me.fromAst(midpoint[dim] + dir[dim] * halfDesiredlength));
desiredEndpoint2.push(me.fromAst(midpoint[dim] - dir[dim] * halfDesiredlength));
}

return {
success: true,
instructions: [{
setDependency: "endpoints",
desiredValue: [desiredEndpoint1, desiredEndpoint2]
}]
}


}
}


stateVariableDefinitions.numericalEndpoints = {
isArray: true,
Expand Down Expand Up @@ -494,32 +605,6 @@ export default class LineSegment extends GraphicalComponent {
}
}

stateVariableDefinitions.length = {
public: true,
shadowingInstructions: {
createComponentOfType: "number",
},
returnDependencies: () => ({
numericalEndpoints: {
dependencyType: "stateVariable",
variableName: "numericalEndpoints"
},
nDimensions: {
dependencyType: "stateVariable",
variableName: "nDimensions",
}
}),
definition({ dependencyValues }) {
let ps = dependencyValues.numericalEndpoints;
let length2 = 0;
for (let dim = 0; dim < dependencyValues.nDimensions; dim++) {
length2 += (ps[1][dim] - ps[0][dim]) ** 2;
}

return { setValue: { length: Math.sqrt(length2) } }
}
}

return stateVariableDefinitions;
}

Expand Down

0 comments on commit ec906e8

Please sign in to comment.