Перейти к содержанию

Лингвистические термы и шкалы

Практические примеры

Серьёзность и отказ от классификации объясняют выбор; аудит шкалы выявляет равные максимумы и пробелы.

Записи результатов показывают все диагностические свойства, поиск терма и явные конструкторы записей.

fuzzyroutines.linguistic

Типизированные неизменяемые лингвистические термы, поиск по шкале и фаззификация.

Поиск по имени либо учитывает регистр, либо использует регистронезависимое сравнение Unicode. Современная фаззификация записывает все степени принадлежности и применяет явные политики разрешения равенств и минимальной уверенности, не меняя исторический API совместимости.

LinguisticTerm dataclass

LinguisticTerm(name: str, fuzzySet: ScalarFuzzySet)

Именованная неизменяемая связь с одним скалярным нечётким множеством.

Attributes:

Name Type Description
name str

Непустое точное имя для поиска и отображения.

fuzzySet ScalarFuzzySet

Современное скалярное нечёткое множество, представляемое термом.

name instance-attribute

name: str

fuzzySet instance-attribute

fuzzySet: ScalarFuzzySet

__post_init__

__post_init__() -> None

Require a non-empty exact name and a modern scalar fuzzy set.

Source code in fuzzyroutines/linguistic.py
100
101
102
103
104
105
106
107
108
109
110
def __post_init__(self) -> None:
    """Require a non-empty exact name and a modern scalar fuzzy set."""

    if not isinstance(self.name, str):
        raise InvalidParameterTypeError("name must be a string")

    if not self.name.strip():
        raise InvalidParameterError("name must contain at least one non-whitespace character")

    if not isinstance(self.fuzzySet, ScalarFuzzySet):
        raise InvalidParameterTypeError("fuzzySet must be a ScalarFuzzySet")

TermMembership dataclass

TermMembership(term: LinguisticTerm, grade: MembershipScalar)

Один лингвистический терм и его проверенная степень принадлежности.

term instance-attribute

grade instance-attribute

__post_init__

__post_init__() -> None

Require a typed term and a valid membership grade.

Source code in fuzzyroutines/linguistic.py
156
157
158
159
160
161
162
def __post_init__(self) -> None:
    """Require a typed term and a valid membership grade."""

    if not isinstance(self.term, LinguisticTerm):
        raise InvalidParameterTypeError("term must be a LinguisticTerm")

    object.__setattr__(self, "grade", _RequireGrade(self.grade, "grade"))

FuzzificationPolicy dataclass

FuzzificationPolicy(tiePolicy: str = 'first', minimumConfidence: MembershipScalar = 0.0, tieTolerance: MembershipScalar = 0.0)

Явная политика классификации с обработкой равных максимумов и минимальной уверенностью.

Attributes:

Name Type Description
tiePolicy str

Выбор при равных или эквивалентных по допуску максимальных принадлежностях: "first", "last" или "all".

minimumConfidence MembershipScalar

При максимальной принадлежности не выше этого порога терм не выбирается. По умолчанию отклоняется только нулевое покрытие.

tieTolerance MembershipScalar

Абсолютная разность принадлежности с максимумом, считающаяся равенством.

tiePolicy class-attribute instance-attribute

tiePolicy: str = 'first'

minimumConfidence class-attribute instance-attribute

minimumConfidence: MembershipScalar = 0.0

tieTolerance class-attribute instance-attribute

tieTolerance: MembershipScalar = 0.0

__post_init__

__post_init__() -> None

Validate the complete classification policy.

Source code in fuzzyroutines/linguistic.py
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
def __post_init__(self) -> None:
    """Validate the complete classification policy."""

    if self.tiePolicy not in TIEPOLICIES:
        raise InvalidParameterError(f"unknown tie policy: {self.tiePolicy!r}")

    object.__setattr__(
        self,
        "minimumConfidence",
        _RequireGrade(self.minimumConfidence, "minimumConfidence"),
    )
    object.__setattr__(
        self,
        "tieTolerance",
        _RequireGrade(self.tieTolerance, "tieTolerance"),
    )

FuzzificationResult dataclass

FuzzificationResult(memberships: tuple[TermMembership, ...], confidence: MembershipScalar, policy: FuzzificationPolicy, tiedTerms: tuple[LinguisticTerm, ...], selectedTerms: tuple[LinguisticTerm, ...])

Полные неизменяемые сведения об одной классификации по шкале.

Attributes:

Name Type Description
memberships tuple[TermMembership, ...]

Одна оценка на каждый объявленный терм шкалы, в заданном порядке.

confidence MembershipScalar

Наибольшая записанная степень принадлежности.

policy FuzzificationPolicy

Политика получения отказа и выбора среди равных максимумов.

tiedTerms tuple[LinguisticTerm, ...]

Максимальные термы после применения tieTolerance либо пустой кортеж, если уверенность не превышает минимум.

selectedTerms tuple[LinguisticTerm, ...]

Термы, выбранные из tiedTerms по tiePolicy, либо пустой кортеж при отсутствии совпадения.

memberships instance-attribute

memberships: tuple[TermMembership, ...]

confidence instance-attribute

confidence: MembershipScalar

policy instance-attribute

tiedTerms instance-attribute

tiedTerms: tuple[LinguisticTerm, ...]

selectedTerms instance-attribute

selectedTerms: tuple[LinguisticTerm, ...]

isMatch property

isMatch: bool

Показывает, выбран ли хотя бы один терм согласно политике.

isTie property

isTie: bool

Показывает, признаны ли несколько термов равными максимумами согласно политике.

__post_init__

__post_init__() -> None

Require internally consistent classification evidence.

Source code in fuzzyroutines/linguistic.py
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
def __post_init__(self) -> None:
    """Require internally consistent classification evidence."""

    if not isinstance(self.memberships, tuple) or not self.memberships:
        raise InvalidParameterTypeError("memberships must be a non-empty tuple")

    if not all(isinstance(value, TermMembership) for value in self.memberships):
        raise InvalidParameterTypeError("memberships must contain only TermMembership values")

    if not isinstance(self.policy, FuzzificationPolicy):
        raise InvalidParameterTypeError("policy must be a FuzzificationPolicy")

    confidence = _RequireGrade(self.confidence, "confidence")
    if confidence != max(cast(float, value.grade) for value in self.memberships):
        raise InvalidParameterError("confidence must equal the maximum membership grade")

    object.__setattr__(self, "confidence", confidence)

    expectedTiedTerms: tuple[LinguisticTerm, ...] = ()
    if confidence > cast(float, self.policy.minimumConfidence):
        expectedTiedTerms = tuple(
            value.term
            for value in self.memberships
            if _IsWithinTieTolerance(
                value.grade,
                confidence,
                self.policy.tieTolerance,
            )
        )

    if self.tiedTerms != expectedTiedTerms:
        raise InvalidParameterError("tiedTerms do not match memberships and policy")

    if self.policy.tiePolicy == "first":
        expectedSelectedTerms = expectedTiedTerms[:1]

    elif self.policy.tiePolicy == "last":
        expectedSelectedTerms = expectedTiedTerms[-1:]

    else:
        expectedSelectedTerms = expectedTiedTerms

    if self.selectedTerms != expectedSelectedTerms:
        raise InvalidParameterError("selectedTerms do not match tiedTerms and policy")

ScaleDiagnosticsPolicy dataclass

ScaleDiagnosticsPolicy(sampleCount: int = 101, membershipThreshold: MembershipScalar = 0.0, partitionTolerance: MembershipScalar = 1e-12)

Явная политика сетки и допусков выборочной диагностики шкалы.

Attributes:

Name Type Description
sampleCount int

Число равноотстоящих точек, включая обе границы области анализа.

membershipThreshold MembershipScalar

Терм активен только при степени строго выше порога. Нет активных термов — пробел; два или больше — перекрытие.

partitionTolerance MembershipScalar

Максимальная допустимая абсолютная разность суммы принадлежностей термов и единицы.

sampleCount class-attribute instance-attribute

sampleCount: int = 101

membershipThreshold class-attribute instance-attribute

membershipThreshold: MembershipScalar = 0.0

partitionTolerance class-attribute instance-attribute

partitionTolerance: MembershipScalar = 1e-12

__post_init__

__post_init__() -> None

Validate the complete sampled-diagnostics policy.

Source code in fuzzyroutines/linguistic.py
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
def __post_init__(self) -> None:
    """Validate the complete sampled-diagnostics policy."""

    if isinstance(self.sampleCount, bool) or not isinstance(self.sampleCount, int):
        raise InvalidParameterTypeError("sampleCount must be an integer")

    if self.sampleCount < 2:
        raise InvalidParameterError("sampleCount must be at least two")

    object.__setattr__(
        self,
        "membershipThreshold",
        _RequireGrade(self.membershipThreshold, "membershipThreshold"),
    )
    object.__setattr__(
        self,
        "partitionTolerance",
        _RequireNonNegativeFiniteReal(
            self.partitionTolerance,
            "partitionTolerance",
        ),
    )

ScaleDiagnosticPoint dataclass

ScaleDiagnosticPoint(coordinate: MembershipScalar, memberships: tuple[TermMembership, ...], membershipThreshold: MembershipScalar)

Неизменяемые выборочные сведения о принадлежностях в одной координате шкалы.

Attributes:

Name Type Description
coordinate MembershipScalar

Конечная координата объявленной диагностической сетки.

memberships tuple[TermMembership, ...]

Одна степень принадлежности для каждого терма шкалы, в заданном порядке.

membershipThreshold MembershipScalar

Строгий порог активности для классификации пробелов и перекрытий.

coordinate instance-attribute

coordinate: MembershipScalar

memberships instance-attribute

memberships: tuple[TermMembership, ...]

membershipThreshold instance-attribute

membershipThreshold: MembershipScalar

maximumMembership property

maximumMembership: MembershipScalar

Наибольшая наблюдаемая принадлежность терма в этой координате.

membershipSum property

membershipSum: float

Сумма принадлежностей всех термов в этой координате с плавающей точкой.

activeTerms property

activeTerms: tuple[LinguisticTerm, ...]

Термы, степени которых строго превышают порог активности.

isGap property

isGap: bool

Не активен ли ни один терм в этой координате выборки.

isOverlap property

isOverlap: bool

Активны ли хотя бы два терма в этой координате выборки.

partitionError property

partitionError: float

Абсолютное выборочное отклонение суммы принадлежностей от единицы.

__post_init__

__post_init__() -> None

Require one finite coordinate and a non-empty membership vector.

Source code in fuzzyroutines/linguistic.py
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
def __post_init__(self) -> None:
    """Require one finite coordinate and a non-empty membership vector."""

    if isinstance(self.coordinate, bool) or not isinstance(self.coordinate, Real):
        raise InvalidParameterTypeError("coordinate must be a real number")

    if not math.isfinite(self.coordinate):
        raise InvalidParameterError("coordinate must be a finite real number")

    if not isinstance(self.memberships, tuple) or not self.memberships:
        raise InvalidParameterTypeError("memberships must be a non-empty tuple")

    if not all(isinstance(value, TermMembership) for value in self.memberships):
        raise InvalidParameterTypeError("memberships must contain only TermMembership values")

    object.__setattr__(
        self,
        "membershipThreshold",
        _RequireGrade(self.membershipThreshold, "membershipThreshold"),
    )

ScaleDiagnosticsResult dataclass

ScaleDiagnosticsResult(analysisDomain: IntegrationDomain, policy: ScaleDiagnosticsPolicy, points: tuple[ScaleDiagnosticPoint, ...])

Неизменяемые выборочные сведения о покрытии, перекрытиях, пробелах и разбиении.

Результат описывает только объявленную конечную сетку и не доказывает непрерывное свойство между её координатами.

Attributes:

Name Type Description
analysisDomain IntegrationDomain

Конечный замкнутый интервал диагностической выборки.

policy ScaleDiagnosticsPolicy

Размер сетки и политика допусков диагностики.

points tuple[ScaleDiagnosticPoint, ...]

Упорядоченные сведения для каждой равноотстоящей координаты сетки.

analysisDomain instance-attribute

analysisDomain: IntegrationDomain

policy instance-attribute

points instance-attribute

points: tuple[ScaleDiagnosticPoint, ...]

gapPoints property

gapPoints: tuple[ScaleDiagnosticPoint, ...]

Точки выборки, где ни один терм не превышает порог активности.

overlapPoints property

overlapPoints: tuple[ScaleDiagnosticPoint, ...]

Точки выборки, в которых хотя бы два терма превышают порог.

gapFraction property

gapFraction: float

Доля точек сетки, классифицированных как пробелы.

overlapFraction property

overlapFraction: float

Доля точек сетки, классифицированных как перекрытия.

minimumCoverage property

minimumCoverage: MembershipScalar

Наименьшая среди максимальных наблюдаемых принадлежностей на сетке анализа.

meanCoverage property

meanCoverage: float

Среднее арифметическое наблюдаемых максимальных принадлежностей.

maximumCoverage property

maximumCoverage: MembershipScalar

Наибольшая среди максимальных наблюдаемых принадлежностей на сетке анализа.

maximumActiveTermCount property

maximumActiveTermCount: int

Наибольшее число одновременно активных термов на сетке выборки.

meanPartitionError property

meanPartitionError: float

Среднее наблюдаемое абсолютное отклонение сумм принадлежностей от единицы.

maximumPartitionError property

maximumPartitionError: float

Наибольшее наблюдаемое абсолютное отклонение суммы принадлежностей от единицы.

isPartitionWithinTolerance property

isPartitionWithinTolerance: bool

Удовлетворяет ли каждая наблюдаемая сумма принадлежностей допуску политики.

__post_init__

__post_init__() -> None

Require a complete ordered grid with stable term membership vectors.

Source code in fuzzyroutines/linguistic.py
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
def __post_init__(self) -> None:
    """Require a complete ordered grid with stable term membership vectors."""

    if not isinstance(self.analysisDomain, IntegrationDomain):
        raise InvalidParameterTypeError("analysisDomain must be an IntegrationDomain")

    if not isinstance(self.policy, ScaleDiagnosticsPolicy):
        raise InvalidParameterTypeError("policy must be a ScaleDiagnosticsPolicy")

    if not isinstance(self.points, tuple):
        raise InvalidParameterTypeError("points must be an explicit tuple")

    if len(self.points) != self.policy.sampleCount:
        raise InvalidParameterError("points must contain exactly policy.sampleCount values")

    if not all(isinstance(point, ScaleDiagnosticPoint) for point in self.points):
        raise InvalidParameterTypeError("points must contain only ScaleDiagnosticPoint values")

    if self.points[0].coordinate != self.analysisDomain.left:
        raise InvalidDomainError("the diagnostic grid must start at analysisDomain.left")

    if self.points[-1].coordinate != self.analysisDomain.right:
        raise InvalidDomainError("the diagnostic grid must end at analysisDomain.right")

    if any(
        cast(float, leftPoint.coordinate) >= cast(float, rightPoint.coordinate)
        for leftPoint, rightPoint in zip(self.points, self.points[1:])
    ):
        raise InvalidDomainError("diagnostic coordinates must be strictly increasing")

    declaredTerms = tuple(value.term for value in self.points[0].memberships)
    for point in self.points:
        if point.membershipThreshold != self.policy.membershipThreshold:
            raise InvalidParameterError("point thresholds must match the diagnostics policy")

        if tuple(value.term for value in point.memberships) != declaredTerms:
            raise InvalidParameterError("every diagnostic point must preserve the same term order")

LinguisticScale dataclass

LinguisticScale(terms: tuple[LinguisticTerm, ...])

Неизменяемый упорядоченный кортеж явно объявленных лингвистических термов.

Attributes:

Name Type Description
terms tuple[LinguisticTerm, ...]

Непустой упорядоченный кортеж с уникальными именами при регистронезависимом сравнении Unicode.

terms instance-attribute

terms: tuple[LinguisticTerm, ...]

__post_init__

__post_init__() -> None

Require typed terms whose names have unambiguous lookup keys.

Source code in fuzzyroutines/linguistic.py
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
def __post_init__(self) -> None:
    """Require typed terms whose names have unambiguous lookup keys."""

    if not isinstance(self.terms, tuple):
        raise InvalidParameterTypeError("terms must be an explicit tuple")

    if not self.terms:
        raise InvalidParameterError("linguistic scale must contain at least one term")

    for termIndex, term in enumerate(self.terms):
        if not isinstance(term, LinguisticTerm):
            raise InvalidParameterTypeError(f"terms[{termIndex}] must be a LinguisticTerm")

    termNames = tuple(term.name.casefold() for term in self.terms)

    if len(set(termNames)) != len(termNames):
        raise InvalidParameterError("linguistic term names must be unique ignoring case")

GetTermByName

GetTermByName(termName: str, exactMatching: bool = True) -> LinguisticTerm | None

Возвращает терм, полное имя которого совпадает с запросом.

Parameters:

Name Type Description Default
termName str

Полное имя искомого терма.

required
exactMatching bool

При True учитывать регистр; иначе сравнивать имена через Unicode case folding.

True

Returns:

Type Description
LinguisticTerm | None

Объявленный объект терма либо None, если полного совпадения нет.

Raises:

Type Description
TypeError

termName не является строкой или exactMatching не является логическим значением.

Notes

Операция не ищет подстроки, префиксы, приближённые совпадения или совпадения по принадлежности.

Source code in fuzzyroutines/linguistic.py
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
def GetTermByName(self, termName: str, exactMatching: bool = True) -> LinguisticTerm | None:
    """Return the term whose complete name matches the query.

    Args:
        termName: Complete term name to retrieve.
        exactMatching: Preserve case when `True`; otherwise compare names
            with Unicode case folding.

    Returns:
        The declared term object, or `None` when no complete name matches.

    Raises:
        TypeError: `termName` is not a string or `exactMatching` is not a
            boolean.

    Notes:
        The operation never performs substring, prefix, approximate, or
        membership-based matching.
    """

    if not isinstance(termName, str):
        raise InvalidParameterTypeError("termName must be a string")

    if not isinstance(exactMatching, bool):
        raise InvalidParameterTypeError("exactMatching must be a boolean")

    lookupName = termName if exactMatching else termName.casefold()

    for term in self.terms:
        declaredName = term.name if exactMatching else term.name.casefold()
        if declaredName == lookupName:
            return term

    return None

Fuzzify

Fuzzify(coordinate: MembershipScalar, policy: FuzzificationPolicy | None = None) -> FuzzificationResult

Классифицирует одну координату с явной семантикой равных максимумов и уверенности.

После проверки принадлежности координаты всем универсальным множествам термов каждый терм вычисляется ровно один раз. Результат всегда предоставляет все упорядоченные степени и максимальную оценку как confidence.

Parameters:

Name Type Description Default
coordinate MembershipScalar

Конечная скалярная координата из универсального множества каждого терма.

required
policy FuzzificationPolicy | None

Явная политика классификации либо политика по умолчанию, выбирающая первый точный максимум и считающая нулевое покрытие отсутствием совпадения.

None

Returns:

Type Description
FuzzificationResult

Неизменяемые степени, уверенность, сведения о равных максимумах и выбранные термы.

Raises:

Type Description
TypeError

Неверный тип policy или coordinate не является вещественным скаляром.

ValueError

coordinate неконечна или вне универсального множества одного из термов.

Source code in fuzzyroutines/linguistic.py
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
def Fuzzify(
    self,
    coordinate: MembershipScalar,
    policy: FuzzificationPolicy | None = None,
) -> FuzzificationResult:
    """Classify one coordinate with explicit tie and confidence semantics.

    Every term is evaluated exactly once after the coordinate is confirmed
    to belong to every term universe. The result always exposes all ordered
    membership scores and the maximum score as `confidence`.

    Args:
        coordinate: Finite scalar coordinate belonging to every term
            universe.
        policy: Explicit classification policy, or the default policy that
            selects the first exact maximum and treats zero coverage as no
            match.

    Returns:
        Immutable scores, confidence, tie evidence, and selected terms.

    Raises:
        TypeError: `policy` has the wrong type or `coordinate` is not a real
            scalar.
        ValueError: `coordinate` is non-finite or outside any term universe.
    """

    if policy is None:
        policy = FuzzificationPolicy()

    if not isinstance(policy, FuzzificationPolicy):
        raise InvalidParameterTypeError("policy must be a FuzzificationPolicy")

    for term in self.terms:
        if not term.fuzzySet.universe.Contains(coordinate):
            raise InvalidDomainError("coordinate must belong to every term universe")

    memberships = tuple(
        TermMembership(term, term.fuzzySet.Membership(coordinate))
        for term in self.terms
    )
    confidence = max(cast(float, membership.grade) for membership in memberships)

    if confidence <= cast(float, policy.minimumConfidence):
        return FuzzificationResult(memberships, confidence, policy, (), ())

    tiedTerms = tuple(
        membership.term
        for membership in memberships
        if _IsWithinTieTolerance(
            membership.grade,
            confidence,
            policy.tieTolerance,
        )
    )

    if policy.tiePolicy == "first":
        selectedTerms = tiedTerms[:1]

    elif policy.tiePolicy == "last":
        selectedTerms = tiedTerms[-1:]

    else:
        selectedTerms = tiedTerms

    return FuzzificationResult(
        memberships,
        confidence,
        policy,
        tiedTerms,
        selectedTerms,
    )

Diagnose

Diagnose(analysisDomain: IntegrationDomain, policy: ScaleDiagnosticsPolicy | None = None) -> ScaleDiagnosticsResult

Проверяет покрытие, перекрытия, пробелы и качество разбиения выборкой на одной сетке.

Каждый терм вычисляется ровно один раз в каждой равноотстоящей координате сетки. Терм активен только при степени строго выше membershipThreshold. Операция читает функции принадлежности, но не меняет термы шкалы, нечёткие множества или аналитические коэффициенты.

Parameters:

Name Type Description Default
analysisDomain IntegrationDomain

Конечный замкнутый интервал, содержащийся в непрерывном универсальном множестве каждого терма.

required
policy ScaleDiagnosticsPolicy | None

Явные число точек и допуски либо политика по умолчанию на 101 точку.

None

Returns:

Type Description
ScaleDiagnosticsResult

Неизменяемые сведения о точках выборки и сводные метрики качества.

Raises:

Type Description
TypeError

Неверный тип области или политики либо терм использует универсальное множество, не являющееся непрерывным.

ValueError

Область выходит за универсальное множество одного из термов.

Notes

Конечная выборка даёт воспроизводимые сведения об объявленной сетке, но не доказывает покрытие или качество разбиения между узлами.

Source code in fuzzyroutines/linguistic.py
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
def Diagnose(
    self,
    analysisDomain: IntegrationDomain,
    policy: ScaleDiagnosticsPolicy | None = None,
) -> ScaleDiagnosticsResult:
    """Sample coverage, overlaps, gaps, and partition quality on one grid.

    Every term is evaluated exactly once per evenly spaced grid coordinate.
    A term is active only when its membership is strictly greater than
    `membershipThreshold`. The operation reads membership functions but
    never changes scale terms, fuzzy sets, or analytical coefficients.

    Args:
        analysisDomain: Finite closed interval contained in every term's
            continuous universe.
        policy: Explicit sample count and tolerances, or the default
            101-point policy.

    Returns:
        Immutable sampled point evidence and aggregate quality metrics.

    Raises:
        TypeError: The domain or policy has the wrong type, or any term
            uses a non-continuous universe.
        ValueError: The domain lies outside any term universe.

    Notes:
        Finite sampling is reproducible evidence for the declared grid; it
        is not proof of coverage or partition quality between grid points.
    """

    if not isinstance(analysisDomain, IntegrationDomain):
        raise InvalidParameterTypeError("analysisDomain must be an IntegrationDomain")

    if policy is None:
        policy = ScaleDiagnosticsPolicy()

    if not isinstance(policy, ScaleDiagnosticsPolicy):
        raise InvalidParameterTypeError("policy must be a ScaleDiagnosticsPolicy")

    for term in self.terms:
        if not isinstance(term.fuzzySet.universe, ContinuousUniverse):
            raise InvalidParameterTypeError("scale diagnostics require ContinuousUniverse terms")

        analysisDomain.ValidateWithin(term.fuzzySet.universe)

    points = tuple(
        ScaleDiagnosticPoint(
            coordinate,
            tuple(
                TermMembership(term, term.fuzzySet.Membership(coordinate))
                for term in self.terms
            ),
            policy.membershipThreshold,
        )
        for coordinate in _BuildDiagnosticCoordinates(
            analysisDomain,
            policy.sampleCount,
        )
    )

    return ScaleDiagnosticsResult(analysisDomain, policy, points)