소문제 6-1 normalizeComposition 의 반환값
normalizeComposition({ paraffins: 2, naphthenes: 1, aromatics: 1 }).paraffins 는 얼마일까요.
- 0.25
- 0.5
- 2
답과 이유 보기
답: B
합계는 4 이므로, 파라핀 비율은 2 / 4 = 0.5 입니다.
제 6 장
제 5 장에서 움직인 시뮬레이터의 내부를, 순수 JavaScript 로 한 단계씩 따라갑니다. 화학 공학의 엄밀 계산이 아니라, 『방향감을 눈에 보이는 형태로 만든 최소 구현』으로 읽는 페이지입니다.
고등학교 화학의 반응식과 몰비에서 출발하는 버전
범위 밖의 값이나 올바르지 않은 값을 먼저 거르는 것.
이 구현에서, 전제를 만족하지 않는 수치를 받았을 때 던지는 예외.
가중치의 합계를 1 로 맞추어 비율로 고치는 것.
실측 예측이 아니라, 방향감을 보기 쉽게 한 간략화 모델.
실제로 동작하고 있는 코어는 다음 JavaScript 입니다. 외부 라이브러리는 사용하지 않고, 전제를 만족하지 않는 입력은 RangeError 로 멈춥니다.
(function (global) {
"use strict";
function assertFinite(name, value) {
if (!Number.isFinite(value)) {
throw new RangeError(name + " must be finite");
}
}
function assertRange(name, value, min, max) {
assertFinite(name, value);
if (value < min || value > max) {
throw new RangeError(name + " must be between " + min + " and " + max);
}
}
function normalizeComposition(raw) {
const paraffins = Number(raw.paraffins);
const naphthenes = Number(raw.naphthenes);
const aromatics = Number(raw.aromatics);
assertFinite("paraffins", paraffins);
assertFinite("naphthenes", naphthenes);
assertFinite("aromatics", aromatics);
if (paraffins < 0 || naphthenes < 0 || aromatics < 0) {
throw new RangeError("composition weights must be non-negative");
}
const total = paraffins + naphthenes + aromatics;
if (total <= 0) {
throw new RangeError("composition weights must sum to a positive number");
}
return {
paraffins: paraffins / total,
naphthenes: naphthenes / total,
aromatics: aromatics / total
};
}
function estimateCutProfile(cut) {
assertRange("cut", cut, 0, 1);
return {
averageCarbonNumber: 5.2 + 5.0 * cut,
boilingStartC: 30 + 55 * cut,
boilingEndC: 95 + 105 * cut
};
}
function estimateIndicators(comp, cut) {
assertRange("cut", cut, 0, 1);
assertRange("paraffins", comp.paraffins, 0, 1);
assertRange("naphthenes", comp.naphthenes, 0, 1);
assertRange("aromatics", comp.aromatics, 0, 1);
const p = comp.paraffins;
const n = comp.naphthenes;
const a = comp.aromatics;
const profile = estimateCutProfile(cut);
return {
averageCarbonNumber: profile.averageCarbonNumber,
boilingStartC: profile.boilingStartC,
boilingEndC: profile.boilingEndC,
volatility: Math.round(100 * (0.75 * (1 - cut) + 0.15 * p + 0.10 * (1 - a))),
density: Math.round(100 * (0.15 + 0.35 * cut + 0.20 * n + 0.30 * a)),
octaneTendency: Math.round(100 * (0.10 + 0.25 * cut + 0.20 * n + 0.35 * a)),
steamCrackingSuitability: Math.round(100 * (0.35 + 0.30 * p + 0.20 * (1 - cut) - 0.15 * a)),
reformingSuitability: Math.round(100 * (0.15 + 0.35 * cut + 0.30 * n + 0.20 * a))
};
}
function describeScenario(comp, cut, indicators) {
if (cut < 0.35 && comp.paraffins >= 0.45) {
return "경질이고 파라핀이 많은 편입니다. 증발하기 쉽고, 수증기 분해 쪽의 읽기가 쉬운 상태입니다.";
}
if (cut >= 0.6 && comp.naphthenes >= 0.35) {
return "중질 쪽이고 나프텐이 두꺼운 편입니다. 개질과의 연결이 보이기 쉬운 상태입니다.";
}
if (comp.aromatics >= 0.35) {
return "방향족이 다소 두꺼운 편입니다. 밀도와 옥탄가의 경향이 올라가기 쉬운 반면, 노출이나 규격의 논점도 의식하고 싶은 상태입니다.";
}
if (indicators.volatility >= 60) {
return "다소 경질 쪽입니다. 증기압이나 인화의 관점을 먼저 의식하면 읽기 쉬워집니다.";
}
return "중간적인 컷과 조성입니다. 하류의 방향은, 전처리나 규격 조건에 따라 흔들린다고 읽을 수 있습니다.";
}
const PRESETS = {
lightParaffinic: {
label: "경질 파라핀 쪽",
cut: 25,
paraffins: 60,
naphthenes: 25,
aromatics: 15
},
balanced: {
label: "밸런스",
cut: 55,
paraffins: 45,
naphthenes: 35,
aromatics: 20
},
reforming: {
label: "개질 지향",
cut: 72,
paraffins: 20,
naphthenes: 50,
aromatics: 30
},
aromaticRich: {
label: "방향족 리치",
cut: 65,
paraffins: 15,
naphthenes: 20,
aromatics: 65
}
};
global.NC101Core = {
assertFinite: assertFinite,
assertRange: assertRange,
normalizeComposition: normalizeComposition,
estimateCutProfile: estimateCutProfile,
estimateIndicators: estimateIndicators,
describeScenario: describeScenario,
PRESETS: PRESETS
};
})(window);
estimateIndicators 안의 계수(0.75, 0.15, 0.10 등)는, 실측 데이터에 기반한 결정값이 아니라 본 강좌의 본문에서 설명한 방향감을 슬라이더 위에서 보기 쉽게 하기 위한 교재용 파라미터입니다. 예를 들어 volatility 의 식에서는:
0.75 × (1 - cut) — 휘발성은 「경질일수록 높다」가 지배적이므로, 컷의 기여를 가장 크게 잡습니다.0.15 × p — 파라핀 비율이 높을수록 휘발하기 쉬운 경향을, 제 2 장의 방향감에 맞춰 중간 가중치로 반영합니다.0.10 × (1 - a) — 방향족이 많으면 상대적으로 휘발성이 내려가는 경향을, 보조적인 작은 가중치로 반영합니다.합계가 대체로 1 이 되도록 손으로 조정한 값입니다. 실프로세스 모델이 아니므로 계수를 바꾸면 거동도 달라집니다. 개조해 보면 어느 축이 무엇을 지배하는지 체감할 수 있습니다.
describeScenario 는 시나리오 문장을 고를 때, 먼저 cut 와 comp 의 원래 값으로 분기하고, 그 후에 indicators.volatility >= 60 을 봅니다. 이는 indicators 경유로도 같은 판정을 재현할 수 있지만, 시나리오 문장의 의도가 「조성·컷의 직관적인 상태」를 말로 표현하는 것이므로, 입력 측에서 먼저 판정하는 편이 읽기 쉽다는 설계 판단입니다. 임계값(0.35, 0.45, 0.6, 60 등)은 교재용 가상값이며, 실프로세스의 기준이 아닙니다.
PRESETS 의 P / N / A 는, 보기 쉬움을 위해 모두 합계 100 이 되도록 손으로 맞추어 두었습니다. 다만 코드 측은 이 합계를 가정하지 않습니다. normalizeComposition 이 반드시 정규화하므로, 합계가 얼마든(양의 수이기만 하면) 같은 결과가 나옵니다. 프리셋을 추가할 때는 합계 100 을 유지하지 않아도 동작하지만, UI 표시와 일치시키기 위해 100 으로 맞추는 것이 무난합니다.
| 코드 | 의미 |
|---|---|
| normalizeComposition | P / N / A 의 원래 가중치를 비율로 고친다 |
| estimateCutProfile | 경질 ↔ 중질을 평균 탄소수와 비점 범위로 사상한다 |
| volatility | 가벼움·증발하기 쉬움의 개념 점수 |
| steamCrackingSuitability | 작게 자르는 맥락과의 궁합 |
| reformingSuitability | 중질 쪽·나프텐 쪽과의 궁합 |
수치는 「엄밀 예측」이 아니라, 본문에서 설명한 방향감을 가시화하기 위해 놓고 있습니다.
코드의 각 줄이 무엇을 하고 있는지를 확인하는 4 문제입니다.
normalizeComposition({ paraffins: 2, naphthenes: 1, aromatics: 1 }).paraffins 는 얼마일까요.
답: B
합계는 4 이므로, 파라핀 비율은 2 / 4 = 0.5 입니다.
estimateCutProfile(0.8).averageCarbonNumber 는 얼마일까요.
답: C
구현은 5.2 + 5.0 × cut 이므로, 5.2 + 4.0 = 9.2 입니다.
estimateIndicators 의 reformingSuitability 를 밀어 올리기 쉬운 입력은 무엇일까요.
답: A
이 개념 모델에서는, 중질 쪽의 컷과 나프텐 비율이 reformingSuitability 를 올리기 쉬운 설계입니다.
음수 가중치 같은 부정 입력이 왔을 때, 이 구현이 던지는 예외는 무엇일까요.
답: B
이 구현에서는, 전제를 만족하지 않는 값은 RangeError 로 멈춥니다.