The demo below starts with a stable matching for three doctors and two hospitals — no blocking pairs exist. Then you add a couple who need to be co-located. Watch how their joint constraint creates a blocking pair in the current assignment, and how fixing it cascades into new instabilities.
<div class="hint">
Three doctors compete for two hospitals. A <b>stable matching</b> has no blocking pair — no doctor and hospital both prefer each other over their current assignment. Add a couple who must <em>both</em> be placed, and watch every candidate matching become unstable.
</div>
<div id="scene">
<div class="panel">
<h3>Current Matching</h3>
<div id="matching-display"></div>
</div>
<div class="panel">
<h3>Preferences</h3>
<div id="prefs-display"></div>
</div>
</div>
<div class="status-box" id="status-box">Click "Check Stability" to start.</div>
<div class="btns">
<button id="btn-couple" type="button">Add couple constraint</button>
<button id="btn-cycle" type="button" disabled>Cycle candidate matchings</button>
<button id="btn-check" type="button">Check stability</button>
<button id="btn-reset" type="button" class="ghost">Reset</button>
</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; font-size: .92rem; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .8rem; line-height: 1.5; }
#scene { display: flex; gap: 1rem; margin-bottom: .8rem; flex-wrap: wrap; }
.panel { flex: 1 1 180px; background: #f0f4f8; border-radius: 10px; padding: .75rem 1rem; }
.panel h3 { margin: 0 0 .5rem; font-size: .88rem; color: #1d3557; text-transform: uppercase; letter-spacing: .05em; }
.match-row { display: flex; align-items: center; gap: .4rem; margin: .28rem 0; font-weight: 600; font-size: .88rem; }
.doc { background: #1d3557; color: #fff; border-radius: 6px; padding: .12rem .45rem; }
.doc.ca { background: #6a4c93; }
.doc.cb { background: #a855f7; }
.hosp { background: #457b9d; color: #fff; border-radius: 6px; padding: .12rem .45rem; }
.arrow { color: #888; }
.unmatched { color: #888; font-style: italic; font-size: .85rem; }
.couple-badge { display: inline-block; background: #6a4c93; color: #fff;
font-size: .7rem; border-radius: 4px; padding: 0 .3rem; margin-left: .25rem; }
.pref-row { margin: .22rem 0; font-size: .83rem; line-height: 1.4; }
.pref-label { font-weight: 700; color: #1d3557; }
.status-box { background: #e8eef3; border-radius: 8px; padding: .6rem .9rem;
margin-bottom: .7rem; min-height: 2.4em; font-size: .88rem; line-height: 1.6; }
.status-box.ok { background: #d4edda; color: #0a7d33; }
.status-box.bad { background: #fde8e8; color: #c92f3c; }
.status-box.warn { background: #fff3cd; color: #7d5a00; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; }
button { font: 600 13px system-ui; padding: .4rem .85rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
button:disabled { opacity: .4; cursor: default; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Matching with Couples demo
//
// Doctors: A1 (couple), A2 (couple), B (single)
// Hospitals: H1 (cap 1), H2 (cap 1); one doctor goes unmatched.
//
// Preferences:
// A1: H1 > H2 > unmatched
// A2: H2 > H1 > unmatched
// B: H1 > H2 > unmatched
// H1 ranks: B > A1 > A2
// H2 ranks: A1 > A2 > B
//
// Singles-only stable matching: A1->H2, A2->unmatched, B->H1
// (no blocking pairs — verified below)
//
// Couple constraint: A1 + A2 must BOTH be placed.
// Joint pref list: (H1,H2) > (H2,H1) > (unmatched,unmatched)
//
// With couple constraint, try every valid assignment where both A1,A2 are placed:
// Option (H1,H2) → A1->H1, A2->H2, B->null: blocking pair B+H1 (B wants H1, H1 prefers B over A1)
// Option (H2,H1) → A1->H2, A2->H1, B->null: blocking pairs A1+H1 and B+H1
// Result: NO stable matching exists when couple constraint is imposed.
// This is Ronn's (1990) NP-hardness instantiated in a 3-doctor example.
const DOCS = ['A1', 'A2', 'B'];
const HOSPS = ['H1', 'H2'];
const docPref = { A1:['H1','H2',null], A2:['H2','H1',null], B:['H1','H2',null] };
const hospPref = { H1:['B','A1','A2'], H2:['A1','A2','B'] };
const couplePref = [['H1','H2'],['H2','H1'],[null,null]];
// Candidates to cycle through once couple mode is on:
// 0: singles-stable (A1->H2,A2->null,B->H1)
// 1: couple option 1 (A1->H1,A2->H2,B->null)
// 2: couple option 2 (A1->H2,A2->H1,B->null)
const CANDIDATES = [
{ A1:'H2', A2:null, B:'H1', label:'Singles-stable: A1→H2, A2→unmatched, B→H1' },
{ A1:'H1', A2:'H2', B:null, label:'Couple option 1: A1→H1, A2→H2, B→unmatched' },
{ A1:'H2', A2:'H1', B:null, label:'Couple option 2: A1→H2, A2→H1, B→unmatched' },
];
let coupleMode = false;
let candidateIdx = 0;
let matching = Object.assign({}, CANDIDATES[0]);
function blockingPairs(m) {
const pairs = [];
for (const d of DOCS) {
const curH = m[d];
const curDocRank = curH === null ? 999 : docPref[d].indexOf(curH);
for (const h of HOSPS) {
if (h === curH) continue;
const altRank = docPref[d].indexOf(h);
if (altRank >= curDocRank) continue;
const incumbent = DOCS.find(x => m[x] === h && x !== d) || null;
const incRank = incumbent === null ? 999 : hospPref[h].indexOf(incumbent);
if (hospPref[h].indexOf(d) < incRank) pairs.push({ doc:d, hosp:h, incumbent });
}
}
return pairs;
}
function coupleOk(m) {
for (const [ca,cb] of couplePref) {
if (ca === m['A1'] && cb === m['A2']) return true;
}
return false;
}
function renderMatching() {
const el = document.getElementById('matching-display');
el.innerHTML = '';
for (const d of DOCS) {
const h = matching[d];
const isA1 = coupleMode && d === 'A1';
const isA2 = coupleMode && d === 'A2';
const badge = (isA1 || isA2) ? '<span class="couple-badge">♥</span>' : '';
el.innerHTML +=
`<div class="match-row">` +
`<span class="doc ${isA1?'ca':isA2?'cb':''}">${d}${badge}</span>` +
`<span class="arrow">→</span>` +
(h ? `<span class="hosp">${h}</span>` : '<span class="unmatched">unmatched</span>') +
`</div>`;
}
}
function renderPrefs() {
const el = document.getElementById('prefs-display');
el.innerHTML = '';
for (const d of DOCS) {
const isA1 = coupleMode && d === 'A1';
const isA2 = coupleMode && d === 'A2';
const badge = (isA1||isA2) ? '<span class="couple-badge">♥</span>' : '';
el.innerHTML += `<div class="pref-row"><span class="pref-label">${d}${badge}:</span> ` +
docPref[d].map(h=>h||'none').join(' > ') + `</div>`;
}
for (const h of HOSPS) {
el.innerHTML += `<div class="pref-row"><span class="pref-label">${h}:</span> ` +
hospPref[h].join(' > ') + `</div>`;
}
if (coupleMode) {
el.innerHTML += fmt('<div class="pref-row" style="margin-top:.4rem;border-top:1px solid #cdd9e3;padding-top:.4rem"><span class="pref-label">Couple (A1+A2):</span><br>%map%</div>', { map: couplePref.map(([a,b])=>`(${a||'none'},${b||'none'})`).join(' > ') });
}
}
function setStatus(msg, cls) {
const el = document.getElementById('status-box');
el.innerHTML = msg;
el.className = 'status-box ' + (cls||'');
}
function check() {
const bp = blockingPairs(matching);
if (!coupleMode) {
if (bp.length === 0) {
setStatus('✓ Stable! No blocking pairs exist in the singles-only case.', 'ok');
} else {
const desc = bp.map(({doc,hosp})=>`<b>${doc}</b> and <b>${hosp}</b>`).join(', ');
setStatus(fmt('✗ Unstable — blocking pair(s): %desc%', { desc: desc }), 'bad');
}
return;
}
const cOk = coupleOk(matching);
const issues = [];
if (!cOk) {
const aH = matching['A1']||'unmatched', bH = matching['A2']||'unmatched';
issues.push(fmt('Couple violated: A1@%aH% + A2@%bH% not on their joint list', { aH: aH, bH: bH }));
}
if (bp.length > 0) {
issues.push(fmt('Blocking pair(s): %map%', { map: bp.map(({doc,hosp})=>`${doc}↔${hosp}`).join(', ') }));
}
if (issues.length === 0) {
setStatus('✓ Stable and couple satisfied! (Try all 3 options — none are stable.)', 'ok');
} else {
setStatus('✗ ' + issues.join(' | ') +
(candidateIdx===2 ? '<br><b>All options checked — no stable matching exists with the couple constraint!</b>' : ''), 'bad');
}
}
document.getElementById('btn-couple').addEventListener('click', () => {
coupleMode = true;
candidateIdx = 0;
matching = Object.assign({}, CANDIDATES[0]);
document.getElementById('btn-couple').disabled = true;
document.getElementById('btn-cycle').disabled = false;
renderMatching();
renderPrefs();
setStatus('Couple constraint added! A1 + A2 must both be placed. Check stability for each candidate matching.', 'warn');
});
document.getElementById('btn-cycle').addEventListener('click', () => {
candidateIdx = (candidateIdx + 1) % CANDIDATES.length;
matching = Object.assign({}, CANDIDATES[candidateIdx]);
renderMatching();
const c = CANDIDATES[candidateIdx];
setStatus(fmt('Now showing: %label%. Press "Check Stability".', { label: c.label }), '');
});
document.getElementById('btn-check').addEventListener('click', check);
document.getElementById('btn-reset').addEventListener('click', () => {
coupleMode = false;
candidateIdx = 0;
matching = Object.assign({}, CANDIDATES[0]);
document.getElementById('btn-couple').disabled = false;
document.getElementById('btn-cycle').disabled = true;
renderMatching();
renderPrefs();
setStatus('Click "Check Stability" to start.', '');
});
// Node-verifiable assertions (run at load; visible in console)
(function verify() {
// 1. Singles-stable matching has 0 blocking pairs
const m0 = CANDIDATES[0];
const bp0 = blockingPairs(m0);
console.assert(bp0.length === 0, 'FAIL: singles-stable should have 0 BP', bp0);
console.log('VERIFY 1: singles-stable BP count =', bp0.length, '(expected 0) ✓');
// 2. Singles-stable violates couple constraint (A2 unmatched)
const c0 = coupleOk(m0);
console.assert(c0 === false, 'FAIL: singles-stable should violate couple constraint');
console.log('VERIFY 2: singles-stable couple ok =', c0, '(expected false) ✓');
// 3. Couple option 1 has blocking pair (B+H1)
const m1 = CANDIDATES[1];
const bp1 = blockingPairs(m1);
const hasB_H1 = bp1.some(x => x.doc==='B' && x.hosp==='H1');
console.assert(hasB_H1, 'FAIL: option1 should have B+H1 blocking pair', bp1);
console.log('VERIFY 3: option1 BP =', bp1.map(x=>x.doc+'+'+x.hosp), '(expected B+H1) ✓');
// 4. Couple option 2 has blocking pairs (A1+H1 and B+H1)
const m2 = CANDIDATES[2];
const bp2 = blockingPairs(m2);
const hasA1_H1 = bp2.some(x => x.doc==='A1' && x.hosp==='H1');
const hasB_H1b = bp2.some(x => x.doc==='B' && x.hosp==='H1');
console.assert(hasA1_H1 && hasB_H1b, 'FAIL: option2 should have A1+H1 and B+H1', bp2);
console.log('VERIFY 4: option2 BP =', bp2.map(x=>x.doc+'+'+x.hosp), '(expected A1+H1, B+H1) ✓');
// 5. Both couple options satisfy couple constraint
console.assert(coupleOk(m1), 'FAIL: option1 should satisfy couple pref (H1,H2)');
console.assert(coupleOk(m2), 'FAIL: option2 should satisfy couple pref (H2,H1)');
console.log('VERIFY 5: both couple options satisfy joint pref ✓');
console.log('All verifications passed. No stable matching with couple constraint exists.');
})();
renderMatching();
renderPrefs();
<div class="hint">
Three doctors compete for two hospitals. A <b>stable matching</b> has no blocking pair — no doctor and hospital both prefer each other over their current assignment. Add a couple who must <em>both</em> be placed, and watch every candidate matching become unstable.
</div>
<div id="scene">
<div class="panel">
<h3>Current Matching</h3>
<div id="matching-display"></div>
</div>
<div class="panel">
<h3>Preferences</h3>
<div id="prefs-display"></div>
</div>
</div>
<div class="status-box" id="status-box">Click "Check Stability" to start.</div>
<div class="btns">
<button id="btn-couple" type="button">Add couple constraint</button>
<button id="btn-cycle" type="button" disabled>Cycle candidate matchings</button>
<button id="btn-check" type="button">Check stability</button>
<button id="btn-reset" type="button" class="ghost">Reset</button>
</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; font-size: .92rem; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .8rem; line-height: 1.5; }
#scene { display: flex; gap: 1rem; margin-bottom: .8rem; flex-wrap: wrap; }
.panel { flex: 1 1 180px; background: #f0f4f8; border-radius: 10px; padding: .75rem 1rem; }
.panel h3 { margin: 0 0 .5rem; font-size: .88rem; color: #1d3557; text-transform: uppercase; letter-spacing: .05em; }
.match-row { display: flex; align-items: center; gap: .4rem; margin: .28rem 0; font-weight: 600; font-size: .88rem; }
.doc { background: #1d3557; color: #fff; border-radius: 6px; padding: .12rem .45rem; }
.doc.ca { background: #6a4c93; }
.doc.cb { background: #a855f7; }
.hosp { background: #457b9d; color: #fff; border-radius: 6px; padding: .12rem .45rem; }
.arrow { color: #888; }
.unmatched { color: #888; font-style: italic; font-size: .85rem; }
.couple-badge { display: inline-block; background: #6a4c93; color: #fff;
font-size: .7rem; border-radius: 4px; padding: 0 .3rem; margin-left: .25rem; }
.pref-row { margin: .22rem 0; font-size: .83rem; line-height: 1.4; }
.pref-label { font-weight: 700; color: #1d3557; }
.status-box { background: #e8eef3; border-radius: 8px; padding: .6rem .9rem;
margin-bottom: .7rem; min-height: 2.4em; font-size: .88rem; line-height: 1.6; }
.status-box.ok { background: #d4edda; color: #0a7d33; }
.status-box.bad { background: #fde8e8; color: #c92f3c; }
.status-box.warn { background: #fff3cd; color: #7d5a00; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; }
button { font: 600 13px system-ui; padding: .4rem .85rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
button:disabled { opacity: .4; cursor: default; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Matching with Couples demo
//
// Doctors: A1 (couple), A2 (couple), B (single)
// Hospitals: H1 (cap 1), H2 (cap 1); one doctor goes unmatched.
//
// Preferences:
// A1: H1 > H2 > unmatched
// A2: H2 > H1 > unmatched
// B: H1 > H2 > unmatched
// H1 ranks: B > A1 > A2
// H2 ranks: A1 > A2 > B
//
// Singles-only stable matching: A1->H2, A2->unmatched, B->H1
// (no blocking pairs — verified below)
//
// Couple constraint: A1 + A2 must BOTH be placed.
// Joint pref list: (H1,H2) > (H2,H1) > (unmatched,unmatched)
//
// With couple constraint, try every valid assignment where both A1,A2 are placed:
// Option (H1,H2) → A1->H1, A2->H2, B->null: blocking pair B+H1 (B wants H1, H1 prefers B over A1)
// Option (H2,H1) → A1->H2, A2->H1, B->null: blocking pairs A1+H1 and B+H1
// Result: NO stable matching exists when couple constraint is imposed.
// This is Ronn's (1990) NP-hardness instantiated in a 3-doctor example.
const DOCS = ['A1', 'A2', 'B'];
const HOSPS = ['H1', 'H2'];
const docPref = { A1:['H1','H2',null], A2:['H2','H1',null], B:['H1','H2',null] };
const hospPref = { H1:['B','A1','A2'], H2:['A1','A2','B'] };
const couplePref = [['H1','H2'],['H2','H1'],[null,null]];
// Candidates to cycle through once couple mode is on:
// 0: singles-stable (A1->H2,A2->null,B->H1)
// 1: couple option 1 (A1->H1,A2->H2,B->null)
// 2: couple option 2 (A1->H2,A2->H1,B->null)
const CANDIDATES = [
{ A1:'H2', A2:null, B:'H1', label:'Singles-stable: A1→H2, A2→unmatched, B→H1' },
{ A1:'H1', A2:'H2', B:null, label:'Couple option 1: A1→H1, A2→H2, B→unmatched' },
{ A1:'H2', A2:'H1', B:null, label:'Couple option 2: A1→H2, A2→H1, B→unmatched' },
];
let coupleMode = false;
let candidateIdx = 0;
let matching = Object.assign({}, CANDIDATES[0]);
function blockingPairs(m) {
const pairs = [];
for (const d of DOCS) {
const curH = m[d];
const curDocRank = curH === null ? 999 : docPref[d].indexOf(curH);
for (const h of HOSPS) {
if (h === curH) continue;
const altRank = docPref[d].indexOf(h);
if (altRank >= curDocRank) continue;
const incumbent = DOCS.find(x => m[x] === h && x !== d) || null;
const incRank = incumbent === null ? 999 : hospPref[h].indexOf(incumbent);
if (hospPref[h].indexOf(d) < incRank) pairs.push({ doc:d, hosp:h, incumbent });
}
}
return pairs;
}
function coupleOk(m) {
for (const [ca,cb] of couplePref) {
if (ca === m['A1'] && cb === m['A2']) return true;
}
return false;
}
function renderMatching() {
const el = document.getElementById('matching-display');
el.innerHTML = '';
for (const d of DOCS) {
const h = matching[d];
const isA1 = coupleMode && d === 'A1';
const isA2 = coupleMode && d === 'A2';
const badge = (isA1 || isA2) ? '<span class="couple-badge">♥</span>' : '';
el.innerHTML +=
`<div class="match-row">` +
`<span class="doc ${isA1?'ca':isA2?'cb':''}">${d}${badge}</span>` +
`<span class="arrow">→</span>` +
(h ? `<span class="hosp">${h}</span>` : '<span class="unmatched">unmatched</span>') +
`</div>`;
}
}
function renderPrefs() {
const el = document.getElementById('prefs-display');
el.innerHTML = '';
for (const d of DOCS) {
const isA1 = coupleMode && d === 'A1';
const isA2 = coupleMode && d === 'A2';
const badge = (isA1||isA2) ? '<span class="couple-badge">♥</span>' : '';
el.innerHTML += `<div class="pref-row"><span class="pref-label">${d}${badge}:</span> ` +
docPref[d].map(h=>h||'none').join(' > ') + `</div>`;
}
for (const h of HOSPS) {
el.innerHTML += `<div class="pref-row"><span class="pref-label">${h}:</span> ` +
hospPref[h].join(' > ') + `</div>`;
}
if (coupleMode) {
el.innerHTML += fmt('<div class="pref-row" style="margin-top:.4rem;border-top:1px solid #cdd9e3;padding-top:.4rem"><span class="pref-label">Couple (A1+A2):</span><br>%map%</div>', { map: couplePref.map(([a,b])=>`(${a||'none'},${b||'none'})`).join(' > ') });
}
}
function setStatus(msg, cls) {
const el = document.getElementById('status-box');
el.innerHTML = msg;
el.className = 'status-box ' + (cls||'');
}
function check() {
const bp = blockingPairs(matching);
if (!coupleMode) {
if (bp.length === 0) {
setStatus('✓ Stable! No blocking pairs exist in the singles-only case.', 'ok');
} else {
const desc = bp.map(({doc,hosp})=>`<b>${doc}</b> and <b>${hosp}</b>`).join(', ');
setStatus(fmt('✗ Unstable — blocking pair(s): %desc%', { desc: desc }), 'bad');
}
return;
}
const cOk = coupleOk(matching);
const issues = [];
if (!cOk) {
const aH = matching['A1']||'unmatched', bH = matching['A2']||'unmatched';
issues.push(fmt('Couple violated: A1@%aH% + A2@%bH% not on their joint list', { aH: aH, bH: bH }));
}
if (bp.length > 0) {
issues.push(fmt('Blocking pair(s): %map%', { map: bp.map(({doc,hosp})=>`${doc}↔${hosp}`).join(', ') }));
}
if (issues.length === 0) {
setStatus('✓ Stable and couple satisfied! (Try all 3 options — none are stable.)', 'ok');
} else {
setStatus('✗ ' + issues.join(' | ') +
(candidateIdx===2 ? '<br><b>All options checked — no stable matching exists with the couple constraint!</b>' : ''), 'bad');
}
}
document.getElementById('btn-couple').addEventListener('click', () => {
coupleMode = true;
candidateIdx = 0;
matching = Object.assign({}, CANDIDATES[0]);
document.getElementById('btn-couple').disabled = true;
document.getElementById('btn-cycle').disabled = false;
renderMatching();
renderPrefs();
setStatus('Couple constraint added! A1 + A2 must both be placed. Check stability for each candidate matching.', 'warn');
});
document.getElementById('btn-cycle').addEventListener('click', () => {
candidateIdx = (candidateIdx + 1) % CANDIDATES.length;
matching = Object.assign({}, CANDIDATES[candidateIdx]);
renderMatching();
const c = CANDIDATES[candidateIdx];
setStatus(fmt('Now showing: %label%. Press "Check Stability".', { label: c.label }), '');
});
document.getElementById('btn-check').addEventListener('click', check);
document.getElementById('btn-reset').addEventListener('click', () => {
coupleMode = false;
candidateIdx = 0;
matching = Object.assign({}, CANDIDATES[0]);
document.getElementById('btn-couple').disabled = false;
document.getElementById('btn-cycle').disabled = true;
renderMatching();
renderPrefs();
setStatus('Click "Check Stability" to start.', '');
});
// Node-verifiable assertions (run at load; visible in console)
(function verify() {
// 1. Singles-stable matching has 0 blocking pairs
const m0 = CANDIDATES[0];
const bp0 = blockingPairs(m0);
console.assert(bp0.length === 0, 'FAIL: singles-stable should have 0 BP', bp0);
console.log('VERIFY 1: singles-stable BP count =', bp0.length, '(expected 0) ✓');
// 2. Singles-stable violates couple constraint (A2 unmatched)
const c0 = coupleOk(m0);
console.assert(c0 === false, 'FAIL: singles-stable should violate couple constraint');
console.log('VERIFY 2: singles-stable couple ok =', c0, '(expected false) ✓');
// 3. Couple option 1 has blocking pair (B+H1)
const m1 = CANDIDATES[1];
const bp1 = blockingPairs(m1);
const hasB_H1 = bp1.some(x => x.doc==='B' && x.hosp==='H1');
console.assert(hasB_H1, 'FAIL: option1 should have B+H1 blocking pair', bp1);
console.log('VERIFY 3: option1 BP =', bp1.map(x=>x.doc+'+'+x.hosp), '(expected B+H1) ✓');
// 4. Couple option 2 has blocking pairs (A1+H1 and B+H1)
const m2 = CANDIDATES[2];
const bp2 = blockingPairs(m2);
const hasA1_H1 = bp2.some(x => x.doc==='A1' && x.hosp==='H1');
const hasB_H1b = bp2.some(x => x.doc==='B' && x.hosp==='H1');
console.assert(hasA1_H1 && hasB_H1b, 'FAIL: option2 should have A1+H1 and B+H1', bp2);
console.log('VERIFY 4: option2 BP =', bp2.map(x=>x.doc+'+'+x.hosp), '(expected A1+H1, B+H1) ✓');
// 5. Both couple options satisfy couple constraint
console.assert(coupleOk(m1), 'FAIL: option1 should satisfy couple pref (H1,H2)');
console.assert(coupleOk(m2), 'FAIL: option2 should satisfy couple pref (H2,H1)');
console.log('VERIFY 5: both couple options satisfy joint pref ✓');
console.log('All verifications passed. No stable matching with couple constraint exists.');
})();
renderMatching();
renderPrefs();
Comments
Loading comments...