Skip to main content

A domain-specific language for defining soft logic constraints in medical/general domains

Project description

Logic Language Documentation

Logo

Overview

The Logic Language is a domain-specific language (DSL) designed for defining soft logic constraints in mammography classification. It allows you to replace hard-coded constraint logic with flexible, interpretable logic scripts that can be modified without changing Python code.

Installation

Install the package using pip:

pip install logic-lang

Requirements

  • Python 3.8 or higher
  • PyTorch 1.9.0 or higher
  • NumPy 1.20.0 or higher

Quick Start

from logic_lang import RuleInterpreter

interpreter = RuleInterpreter()
features = {"predictions": torch.tensor([[0.8, 0.1, 0.1]])} 
script = "constraint exactly_one(predictions);" # also can load from .logic files
constraint_set = interpreter.execute(script, features)

Syntax Reference

Comments

Comments start with # and continue to the end of the line:

# This is a comment
define findings_L = mass_L | mc_L  # Inline comment

Variable Definitions

Define new variables using logical combinations of existing features:

define variable_name = expression

Constant Definitions

Define constants for reusable literal values:

const constant_name = value

Variable Expectations

Declare which variables (features) the script expects to be provided:

expect variable_name
expect variable1, variable2, variable3

Examples:

# Declare expected variables at the beginning of the script
expect left_birads, right_birads, mass_L, mc_L

# Or declare them individually
expect comp
expect risk_score

# Define constants for thresholds
const high_threshold = 0.8
const low_threshold = 0.2
const birads_cutoff = 4

# Basic logical operations with literals
define findings_L = mass_L | mc_L
define high_risk = risk_score > 0.7  # Using literal number
define moderate_risk = risk_score > low_threshold  # Using constant

# Function calls with mixed literals and variables
define high_birads = sum(birads_L, [4, 5, 6])
define threshold_check = risk_score >= high_threshold

Constraints

Define constraints that will be enforced during training:

constraint expression [weight=value] [transform="type"] [param=value ...]

Examples:

# Basic constraint
constraint exactly_one(birads_L)

# Constraint with weight and transform
constraint findings_L >> high_birads weight=0.7 transform="logbarrier"

# Constraint with multiple parameters
constraint exactly_one(comp) weight=1.5 transform="hinge" alpha=2.0

Operators

Logical Operators (in order of precedence, lowest to highest)

  1. Implication (>>): A >> B (if A then B)

    constraint findings_L >> high_birads_L
    
  2. OR (|): A | B (A or B)

    define findings = mass_L | mc_L
    
  3. XOR (^): A ^ B (A exclusive or B)

    define exclusive = mass_L ^ mc_L
    
  4. Comparison Operators: >, <, ==, >=, <=

    define high_risk = risk_score > threshold_value
    define similar_scores = score_a == score_b
    define within_range = score >= min_val & score <= max_val
    
  5. AND (&): A & B (A and B)

    define strict_findings = mass_L & high_confidence
    
  6. AND_n (& variable): AND across all elements in a tensor

    # All radiologists must agree (consensus)
    define consensus = & radiologist_assessments
    
    # All imaging modalities must show findings
    define all_modalities_positive = & imaging_results
    
  7. OR_n (| variable): OR across all elements in a tensor

    # Any radiologist found something
    define any_concern = | radiologist_assessments
    
    # Any imaging modality shows findings
    define any_positive = | imaging_results
    
  8. NOT (~): ~A (not A)

    define no_findings = ~findings_L
    
  9. Indexing (variable[...]): Access tensor elements using numpy/pytorch syntax

    # Integer indexing
    define first_patient = patient_data[0]
    define specific_element = matrix[1, 2]
    define last_radiologist = assessments[2]
    
    # Slice indexing
    define first_two_patients = patient_data[:2]
    define middle_columns = matrix[:, 1:3]
    define every_other = data[::2]
    define specific_range = tensor[1:4]
    
    # Multi-dimensional indexing
    define patient_features = batch_data[0, :, 2]
    define view_subset = assessments[:, 1, :]
    

Parentheses

Use parentheses to override operator precedence:

define complex = (mass_L | mc_L) & ~(birads_L >> findings_L)

Statement Separation

Semicolons

You can use semicolons (;) to separate multiple statements on a single line, similar to Python:

# Multiple statements on one line
expect a, b; define c = a | b; constraint c

# Mix of semicolons and newlines
const threshold = 0.5; expect risk_score
define high_risk = risk_score > threshold
constraint high_risk weight=0.8

# Multiple constants and definitions
const low = 0.2; const high = 0.8; define range_check = value >= low & value <= high

Line-based Separation

Statements can also be separated by newlines (traditional approach):

expect findings_L, findings_R
define bilateral = findings_L & findings_R
constraint bilateral weight=0.6

Trailing Semicolons

Trailing semicolons are optional and ignored:

expect variables;
define result = expression;
constraint result;

Built-in Functions

sum(probabilities, indices)

Sum probabilities for specified class indices:

define high_birads_L = sum(birads_L, [4, 5, 6])
define very_high_birads = sum(birads_L, [5, 6])

exactly_one(probabilities)

Create exactly-one constraint for categorical probabilities:

constraint exactly_one(birads_L) weight=1.0

mutual_exclusion(...probabilities)

Create mutual exclusion constraint between multiple probabilities:

constraint mutual_exclusion(mass_L, mc_L) weight=0.5

at_least_k(probabilities, k)

Create constraint that at least k elements must be true:

define min_two_findings = at_least_k(findings_combined, 2)
constraint min_two_findings weight=0.6

at_most_k(probabilities, k)

Create constraint that at most k elements can be true:

define max_one_high_birads = at_most_k(high_birads_indicators, 1)
constraint max_one_high_birads weight=0.7

exactly_k(probabilities, k)

Create constraint that exactly k elements must be true:

define exactly_two_radiologists = exactly_k(radiologist_agreement, 2)
constraint exactly_two_radiologists weight=0.8

threshold_implication(antecedent, consequent, threshold)

Create threshold-based implication constraint:

define strong_implication = threshold_implication(high_risk_L, findings_L, 0.7)
constraint strong_implication weight=0.9

conditional_probability(condition, event, target_prob)

Create conditional probability constraint:

define conditional_findings = conditional_probability(high_birads_L, findings_L, 0.85)
constraint conditional_findings weight=0.8

clamp(tensor, min_val, max_val)

Clamp tensor values to specified range:

define clamped_mass = clamp(mass_L, 0.1, 0.9)

threshold(tensor, threshold)

Apply threshold to tensor:

define binary_mass = threshold(mass_L, 0.5)

greater_than(left, right)

Create soft greater than comparison between two tensors:

define high_confidence = greater_than(confidence, baseline)

less_than(left, right)

Create soft less than comparison between two tensors:

define low_risk = less_than(risk_score, threshold_low)

equals(left, right)

Create soft equality comparison between two tensors:

define similar_scores = equals(score_a, score_b)

threshold_constraint(tensor, threshold, operator)

Create threshold constraint with specified comparison operator:

define high_birads = threshold_constraint(birads_score, 0.7, ">")
define exact_match = threshold_constraint(prediction, 0.5, "==")
define within_bounds = threshold_constraint(value, 0.3, ">=")

Data Types

Numbers

Integer or floating-point numbers can be used directly in expressions:

define high_risk = risk_score > 0.8
define moderate = value >= 0.3 & value <= 0.7
constraint threshold_check weight=1.5  # Literal number as parameter

Strings

Text values enclosed in quotes:

transform="logbarrier"
transform='hinge'
const model_type = "transformer"

Lists

Arrays of values:

[1, 2, 3]
[4, 5, 6]
const important_classes = [4, 5, 6]  # Can store list constants

Mixed Type Expressions

The logic language automatically handles mixed types in expressions:

# Tensor compared with literal number
define high_values = predictions > 0.5

# Tensor compared with constant
const threshold = 0.7
define above_threshold = scores >= threshold

# Combining constants and variables
const low_cut = 0.2
const high_cut = 0.8
define in_range = (values >= low_cut) & (values <= high_cut)

Constraint Parameters

weight (float)

Relative importance of the constraint:

constraint exactly_one(birads_L) weight=2.0  # Higher weight = more important

transform (string)

Loss transformation method:

  • "logbarrier": Logarithmic barrier (default, smooth penalties)
  • "hinge": Hinge loss (softer penalties)
  • "linear": Linear loss (proportional penalties)
constraint findings >> high_birads transform="hinge"

Custom Parameters

Additional parameters specific to constraint types:

constraint exactly_one(birads_L) weight=1.0 alpha=2.0 beta=0.5

Complete Example

# Mammography Constraint Rules
# ============================

# Declare expected variables from model output
expect mass_L, mass_R, mc_L, mc_R
expect birads_L, birads_R, birads_score_L, birads_score_R
expect comp

# Define constants for reusable thresholds
const high_risk_threshold = 0.7
const low_risk_threshold = 0.3
const birads_high_cutoff = 4
const birads_very_high_cutoff = 5

# Feature definitions - combine findings per breast
define findings_L = mass_L | mc_L
define findings_R = mass_R | mc_R

# BI-RADS probability groups using constants
define high_birads_L = sum(birads_L, [4, 5, 6])
define high_birads_R = sum(birads_R, [4, 5, 6])
define very_high_birads_L = sum(birads_L, [5, 6])
define very_high_birads_R = sum(birads_R, [5, 6])
define low_birads_L = sum(birads_L, [1, 2])
define low_birads_R = sum(birads_R, [1, 2])

# Threshold-based risk assessments using literals and constants
define high_risk_L = birads_score_L > high_risk_threshold
define high_risk_R = birads_score_R > high_risk_threshold  
define very_low_risk_L = birads_score_L < low_risk_threshold
define very_low_risk_R = birads_score_R < low_risk_threshold
define balanced_assessment = equals(risk_L, risk_R)

# Range constraints using multiple comparisons with literals
define valid_risk_range_L = (birads_score_L >= 0.0) & (birads_score_L <= 1.0)
define valid_risk_range_R = (birads_score_R >= 0.0) & (birads_score_R <= 1.0)

# No findings (negation of findings)
define no_findings_L = ~findings_L
define no_findings_R = ~findings_R

# Categorical exclusivity constraints
constraint exactly_one(birads_L) weight=1.0 transform="logbarrier"
constraint exactly_one(birads_R) weight=1.0 transform="logbarrier"
constraint exactly_one(comp) weight=0.7 transform="logbarrier"

# Logical implication constraints using threshold variables
constraint high_risk_L >> findings_L weight=0.8 transform="logbarrier"
constraint high_risk_R >> findings_R weight=0.8 transform="logbarrier"

# Very High BI-RADS (5-6) -> Findings  
constraint very_high_birads_L >> findings_L weight=0.7 transform="logbarrier"
constraint very_high_birads_R >> findings_R weight=0.7 transform="logbarrier"

# Low BI-RADS with literal thresholds -> No findings (gentle constraint)
constraint very_low_risk_L >> no_findings_L weight=0.3 transform="logbarrier"
constraint very_low_risk_R >> no_findings_R weight=0.3 transform="logbarrier"

# Range validation constraints
constraint valid_risk_range_L weight=2.0 transform="logbarrier"
constraint valid_risk_range_R weight=2.0 transform="logbarrier"

# Comparison-based constraints using constants
constraint balanced_assessment weight=0.4 transform="hinge"

Usage Patterns

1. Variable Expectations

Declare required variables at the beginning of scripts for better error handling:

# Declare all expected model outputs in one line
expect left_mass_prob, right_mass_prob, left_birads, right_birads, composition

# Now use these variables with confidence
define findings_L = left_mass_prob > 0.5
constraint exactly_one(left_birads)

2. Categorical Constraints

Ensure exactly one category is selected:

constraint exactly_one(birads_L) weight=1.0
constraint exactly_one(composition) weight=0.8

2. Implication Rules

Model domain knowledge as if-then relationships:

# If findings present, then high BI-RADS likely
constraint findings_L >> high_birads_L weight=0.7

# If very high BI-RADS, then findings must be present
constraint very_high_birads_L >> findings_L weight=0.8

3. Mutual Exclusion

Prevent conflicting classifications:

constraint mutual_exclusion(mass_L, calc_L) weight=0.5

4. Threshold Rules

Apply domain-specific thresholds:

define suspicious = threshold(combined_score, 0.7)
constraint suspicious >> high_birads weight=0.6

5. Comparison Constraints

Use soft comparison operators for ordinal and threshold relationships:

# Risk stratification with thresholds
define high_risk = risk_score > 0.8
define low_risk = risk_score < 0.2
constraint high_risk >> findings weight=0.7

6. Consensus and Agreement (AND_n)

Model situations where all elements must be true:

# All radiologists must agree for high confidence
define consensus = & radiologist_assessments
constraint consensus > 0.7 >> definitive_diagnosis weight=0.9

# All imaging modalities must show findings
define multi_modal_positive = & imaging_results
constraint multi_modal_positive >> high_confidence weight=0.8

7. Any Evidence Detection (OR_n)

Model situations where any element being true is significant:

# Any radiologist expressing concern triggers review
define any_concern = | radiologist_assessments  
constraint any_concern > 0.5 >> requires_review weight=0.6

# Any modality showing findings suggests pathology
define any_positive = | imaging_modalities
constraint any_positive >> potential_pathology weight=0.7

8. Tensor Indexing and Slicing

Access specific elements, patients, or subsets of multi-dimensional data:

# Patient-specific analysis
define patient_0_risk = patient_risks[0]
define patient_1_findings = findings[1, :]
constraint patient_0_risk > 0.8 >> patient_0_findings weight=1.0

# View-specific mammography analysis
define cc_assessments = assessments[:, 0, :]  # CC view for all patients
define mlo_assessments = assessments[:, 1, :]  # MLO view for all patients
define cc_consensus = & cc_assessments
define mlo_consensus = & mlo_assessments
constraint cc_consensus & mlo_consensus >> high_confidence weight=0.9

# Radiologist-specific consistency
define senior_opinions = assessments[:, :, 0]  # Senior across all views
define resident_opinions = assessments[:, :, 1]  # Resident across all views
define senior_consistency = & senior_opinions
constraint senior_consistency weight=0.8

# Subset analysis
define high_risk_patients = patient_data[:3]  # First 3 patients
define feature_subset = features[:, 2:5]  # Specific feature range
define consensus_subset = & high_risk_patients
constraint consensus_subset >> intensive_monitoring weight=1.0


# Equality constraints for consistency
define balanced_breasts = equals(risk_L, risk_R)
constraint balanced_breasts weight=0.3 transform="hinge"

# Range constraints using multiple comparisons
define valid_range = (score >= 0.1) & (score <= 0.9)
constraint valid_range weight=1.0

9. Ordinal Relationships

Model ordered classifications with comparison operators:

# BI-RADS ordering constraints
define birads_3_higher = birads_3 >= birads_2
define birads_4_higher = birads_4 >= birads_3
constraint birads_3_higher & birads_4_higher weight=0.8

Error Handling

The logic language provides helpful error messages for common issues:

Syntax Errors

define x = mass_L |  # Error: Missing right operand

Undefined Variables

define x = undefined_var  # Error: Variable 'undefined_var' is not defined

Type Mismatches

constraint exactly_one(5)  # Error: Expected Truth object, got number

Invalid Functions

define x = unknown_func()  # Error: Unknown function 'unknown_func'

Advanced Features

Custom Functions

Add domain-specific functions to the interpreter:

def custom_risk_score(mass_prob, calc_prob, birads_prob):
    # Custom risk calculation
    return combined_risk

interpreter.add_builtin_function('risk_score', custom_risk_score)

Dynamic Rule Updates

Modify rules at runtime:

loss_fn.update_rules(new_rules_string)

Multiple Semantics

Choose different logical semantics:

  • Gödel: min/max operations (sharp decisions)
  • Łukasiewicz: bounded sum operations (smoother)
  • Product: multiplication operations (independent probabilities)
loss_fn = RuleMammoLoss(
    feature_indices=indices,
    rules=rules,
    semantics="lukasiewicz"  # or "godel", "product"
)

Best Practices

  1. Start Simple: Begin with basic constraints and add complexity gradually
  2. Use Comments: Document the medical reasoning behind each constraint
  3. Test Incrementally: Add constraints one at a time and validate behavior
  4. Meaningful Names: Use descriptive variable names that reflect medical concepts
  5. Balanced Weights: Start with equal weights and adjust based on domain importance
  6. Appropriate Transforms: Use "logbarrier" for strict constraints, "hinge" for softer ones

Migration from Hard-coded Constraints

To convert existing hard-coded constraints to logic language:

  1. Identify logical patterns in your constraint code
  2. Extract variable definitions for reused expressions
  3. Convert constraints to logic language syntax
  4. Test equivalence with the original implementation
  5. Refine and optimize weights and transforms

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

logic_lang-0.1.2.tar.gz (41.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

logic_lang-0.1.2-py3-none-any.whl (37.4 kB view details)

Uploaded Python 3

File details

Details for the file logic_lang-0.1.2.tar.gz.

File metadata

  • Download URL: logic_lang-0.1.2.tar.gz
  • Upload date:
  • Size: 41.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.11.8

File hashes

Hashes for logic_lang-0.1.2.tar.gz
Algorithm Hash digest
SHA256 c71b2ee7ffbf17765cfd82056699e1e50d8155bd58ca498906c35c022d6e208e
MD5 cfee30001c49848c93cb36a1bfba6d0a
BLAKE2b-256 feaf49e9908236d0cb0711a7f3ec3cfe8dc2bca09e003e99bb3a39a57e5f026d

See more details on using hashes here.

File details

Details for the file logic_lang-0.1.2-py3-none-any.whl.

File metadata

  • Download URL: logic_lang-0.1.2-py3-none-any.whl
  • Upload date:
  • Size: 37.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.11.8

File hashes

Hashes for logic_lang-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 71fad2603a322a0ff06d48e66cb4d4323124b2380361a51639305c52f40a4a17
MD5 0ae9e8d640f449effb210454493a40e0
BLAKE2b-256 c8df534b8a65ad12ecbd1e092418cda82dfe571ef575eb4e7fd6e7a8be7412a0

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page