Module: Causalontology::Semantics
- Defined in:
- lib/causalontology/semantics.rb
Constant Summary collapse
- UNIT_SECONDS =
Rule 4: the fixed unit-conversion constants (average Gregorian values).
{ "instant" => 0, "seconds" => 1, "minutes" => 60, "hours" => 3600, "days" => 86400, "weeks" => 604800, "months" => 2629746, "years" => 31556952, }.freeze
- ORDINAL_UNITS =
3.0.0: the ordinal (dimensionless) temporal units. A tick is a discrete step with NO wall-clock mapping; a tick window is ordered by integer comparison, and an ordinal window and a wall-clock window are DIFFERENT DIMENSIONS that do not compare (mixing them is never within-window and never overlapping).
Set.new(["ticks"]).freeze
- ENRICHMENT_FIELDS =
Rule 12: enrichment field-to-kind validity and entry shapes. Two occurrent forms added in 2.0.0.
{ "aliases" => [["occurrent", "continuant"], "alias"], "participants" => [["occurrent"], "continuant"], "subsumes" => [["continuant"], "continuant"], "part_of" => [["continuant"], "continuant"], "realized_in" => [["realizable"], "occurrent"], "occurrent_subsumes" => [["occurrent"], "occurrent"], "occurrent_part_of" => [["occurrent"], "occurrent"], }.freeze
- CRO_OPTIONAL_FIELDS =
["mechanism", "temporal", "modality", "context"].freeze
- POSITIVE =
Rule 6 (amended): necessary, sufficient, contributory, enabling are mutually compatible; preventive opposes all four.
Set.new(["necessary", "sufficient", "contributory", "enabling"]).freeze
Class Method Summary collapse
-
.admissible(cro, elapsed) ⇒ Object
Rule 4: temporal admissibility.
-
.bridge_closure(occurrent_id, bridges) ⇒ Object
ALGORITHM A.
-
.bridge_wellformed(bridge, occ_map, stratum_map) ⇒ Object
Rule 14 / N3.2.1: Bridge well-formedness.
-
.classify_cro(cro, occ_map, stratum_map) ⇒ Object
ALGORITHM C (Rule 15): "intra_stratal" | "adjacent_stratal" | "skipping" | "mixed" | "unclassifiable" | "scheme_mismatch".
-
.conduit_wellformed(conduit, port_map, cro_map = nil) ⇒ Object
Rule 17 / N4.2.1-2: Conduit well-formedness.
-
.conflicts(a, b) ⇒ Object
Rule 6: the formal conflict test.
- .contexts_compatible(a, b) ⇒ Object
-
.covering_law_mismatch(tcc, token_map, law) ⇒ Object
Rule 20: true iff the token claim's cause/effect tokens do not instantiate the covering law's causes/effects (surfaces covering_law_mismatch).
-
.delay_within_window(actual_delay, temporal) ⇒ Object
ALGORITHM E (Rule 20): does an observed delay fall within a covering law's temporal window? Inclusive at both ends (N12.5.2).
-
.dimension(unit) ⇒ Object
3.0.0: "ordinal" for a tick-like unit, else "wallclock".
-
.endpoints_mixed(cro, occ_map) ⇒ Object
True iff causes or effects span more than one distinct stratum (surfaces mixed_stratal_endpoints, an invitation; N12.3.2).
-
.has_cycle(edges) ⇒ Object
Rules 4 / 6.1: true iff a directed graph (node -> iterable of successors) has a cycle.
-
.hierarchy_consistent(parent, members, bridges = []) ⇒ Object
ALGORITHM B (amended Rule 7): "consistent" | "inconsistent" | "indeterminate", ACROSS STRATA via bridged reachability.
-
.is_partial(cro) ⇒ Object
[partial, missing] - which optional CRO fields are unspecified.
- .kind_of_id(identifier) ⇒ Object
-
.magnitude(value, unit) ⇒ Object
3.0.0: a comparable magnitude within ONE dimension: raw tick count for an ordinal unit, seconds for a wall-clock unit.
- .path_exists(edges, src, dst) ⇒ Object
-
.prediction_pairing_mismatch(error, predicted, observed) ⇒ Object
4.0.0 Rule 24: prediction-to-observation pairing.
-
.refinement_valid(child, parent) ⇒ Object
Rule 3: [ok, reason] - is child a valid refinement of parent?.
-
.retrocausal(tcc, token_map) ⇒ Object
Rule 21: true iff any cause token starts after any effect token (HARD; retrocausal_claim).
-
.seam_home(seam, occ_map, stratum_map) ⇒ Object
THE HOME RULE (3.0.0): a Cross Stratal Seam belongs to the COARSEST stratum it touches - the endpoint of the greater ordinal.
-
.seam_wellformed(seam, occ_map, stratum_map) ⇒ Object
3.0.0 Rule 22 / Algorithm F: Cross Stratal Seam well-formedness.
-
.skip_gaps(cro, classification) ⇒ Object
ALGORITHM D (Rule 16): the gaps a Causal Relation Object surfaces for the skip decision.
-
.state_gaps(state, quality) ⇒ Object
Rule 19 / N5.3.1-2: the HARD gaps a state assertion surfaces against its quality: value_type_mismatch and/or unit_mismatch.
-
.to_seconds(duration, unit) ⇒ Object
ALGORITHM E helper: normalize a delay to seconds by the fixed table.
-
.validate_semantics(obj, kind = nil) ⇒ Object
[ok, reasons] - the locally checkable semantic rules.
- .window_overlap(a, b) ⇒ Object
Class Method Details
.admissible(cro, elapsed) ⇒ Object
Rule 4: temporal admissibility. For a wall-clock window elapsed is in
seconds; for an ordinal ('ticks') window elapsed is a tick count.
Ordering is by magnitude WITHIN the window's own dimension (3.0.0).
166 167 168 169 170 171 172 |
# File 'lib/causalontology/semantics.rb', line 166 def admissible(cro, elapsed) t = cro["temporal"] return true if t.nil? # no window imposes no constraint lo = magnitude(t["minimum_delay"], t["unit"]) hi = magnitude(t["maximum_delay"], t["unit"]) lo <= elapsed && elapsed <= hi end |
.bridge_closure(occurrent_id, bridges) ⇒ Object
ALGORITHM A. Every finer occurrent an occurrent resolves to, following
Bridges downward, transitively. Includes the starting occurrent
(N12.1.1). bridges is any iterable of bridge objects. The visited guard
(N12.1.2) prevents an infinite loop on malformed cyclic data.
242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 |
# File 'lib/causalontology/semantics.rb', line 242 def bridge_closure(occurrent_id, bridges) result = Set.new([occurrent_id]) frontier = [occurrent_id] visited = Set.new coarse_index = {} bridges.each { |b| (coarse_index[b["coarse"]] ||= []) << b } until frontier.empty? current = frontier.pop next if visited.include?(current) visited << current (coarse_index[current] || []).each do |b| b["fine"].each do |f| result << f frontier << f end end end result end |
.bridge_wellformed(bridge, occ_map, stratum_map) ⇒ Object
Rule 14 / N3.2.1: Bridge well-formedness. [ok, reason]. All of (a)-(e) of N3.2.1 must hold, else malformed_bridge.
398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 |
# File 'lib/causalontology/semantics.rb', line 398 def bridge_wellformed(bridge, occ_map, stratum_map) coarse = occ_map[bridge["coarse"]] || {} cs = coarse["stratum"] return [false, "malformed_bridge: coarse has no stratum (a)"] if cs.nil? fine_strata = bridge["fine"].map { |f| (occ_map[f] || {})["stratum"] } if fine_strata.any?(&:nil?) return [false, "malformed_bridge: a fine member has no stratum (b)"] end if Set.new(fine_strata).length != 1 return [false, "malformed_bridge: fine members span >1 stratum (c)"] end fs = fine_strata[0] if stratum_map[cs]["scheme"] != stratum_map[fs]["scheme"] return [false, "malformed_bridge: coarse and fine differ in scheme (d)"] end unless stratum_map[cs]["ordinal"] > stratum_map[fs]["ordinal"] return [false, "malformed_bridge: coarse ordinal not > fine ordinal (e)"] end [true, "well-formed bridge"] end |
.classify_cro(cro, occ_map, stratum_map) ⇒ Object
ALGORITHM C (Rule 15): "intra_stratal" | "adjacent_stratal" | "skipping" | "mixed" | "unclassifiable" | "scheme_mismatch". Derived, never asserted; recompute on ingest (N12.3.1).
310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 |
# File 'lib/causalontology/semantics.rb', line 310 def classify_cro(cro, occ_map, stratum_map) stratum_of = lambda { |occ_id| (occ_map[occ_id] || {})["stratum"] } cause_strata = cro["causes"].map { |c| stratum_of.call(c) } effect_strata = cro["effects"].map { |e| stratum_of.call(e) } if (cause_strata + effect_strata).any?(&:nil?) return "unclassifiable" # surface unstratified_occurrent (invitation) end all_strata = Set.new(cause_strata) | Set.new(effect_strata) schemes = Set.new(all_strata.map { |s| stratum_map[s]["scheme"] }) return "scheme_mismatch" if schemes.length > 1 # HARD c_ord = cause_strata.map { |s| stratum_map[s]["ordinal"] } e_ord = effect_strata.map { |s| stratum_map[s]["ordinal"] } if c_ord.max == c_ord.min && c_ord.min == e_ord.max && e_ord.max == e_ord.min return "intra_stratal" end pairs = c_ord.product(e_ord).map { |i, j| (i - j).abs } gap = pairs.min span = pairs.max return "adjacent_stratal" if span == 1 return "skipping" if gap > 1 "mixed" # some pairs adjacent, some skipping end |
.conduit_wellformed(conduit, port_map, cro_map = nil) ⇒ Object
Rule 17 / N4.2.1-2: Conduit well-formedness. [ok, reason]. N4.2.1 with the transform exception of N4.2.2.
488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 |
# File 'lib/causalontology/semantics.rb', line 488 def conduit_wellformed(conduit, port_map, cro_map = nil) frm = port_map[conduit["from"]] to = port_map[conduit["to"]] if frm.nil? || to.nil? return [false, "malformed_conduit: dangling port reference"] end unless ["out", "bidirectional"].include?(frm["direction"]) return [false, "malformed_conduit: from port is not out/bidirectional (a)"] end unless ["in", "bidirectional"].include?(to["direction"]) return [false, "malformed_conduit: to port is not in/bidirectional (b)"] end carries = conduit["carries"] unless carries.all? { |o| frm["accepts"].include?(o) } return [false, "malformed_conduit: carries not accepted by from (c)"] end transform = conduit["transform"] if transform.nil? unless carries.all? { |o| to["accepts"].include?(o) } return [false, "malformed_conduit: carries not accepted by to (d)"] end else law = (cro_map || {})[transform] unless law.nil? unless law["effects"].all? { |o| to["accepts"].include?(o) } return [false, "malformed_conduit: transform effects not " \ "accepted by to (d, relaxed per N4.2.2)"] end end end [true, "well-formed conduit"] end |
.conflicts(a, b) ⇒ Object
Rule 6: the formal conflict test.
197 198 199 200 201 202 203 204 205 206 |
# File 'lib/causalontology/semantics.rb', line 197 def conflicts(a, b) return false if Set.new(a["causes"]) != Set.new(b["causes"]) return false if Set.new(a["effects"]) != Set.new(b["effects"]) return false unless contexts_compatible(a, b) return false unless window_overlap(a, b) ma = a["modality"] mb = b["modality"] (ma == "preventive" && POSITIVE.include?(mb)) || (mb == "preventive" && POSITIVE.include?(ma)) end |
.contexts_compatible(a, b) ⇒ Object
187 188 189 190 191 192 193 194 |
# File 'lib/causalontology/semantics.rb', line 187 def contexts_compatible(a, b) ca = a["context"] cb = b["context"] return true if ca.nil? || ca.empty? || cb.nil? || cb.empty? sa = Set.new(ca) sb = Set.new(cb) sa == sb || sa.subset?(sb) || sb.subset?(sa) end |
.covering_law_mismatch(tcc, token_map, law) ⇒ Object
Rule 20: true iff the token claim's cause/effect tokens do not instantiate the covering law's causes/effects (surfaces covering_law_mismatch).
542 543 544 545 546 547 548 549 550 551 552 553 |
# File 'lib/causalontology/semantics.rb', line 542 def covering_law_mismatch(tcc, token_map, law) return false if law.nil? law_causes = Set.new(law["causes"]) law_effects = Set.new(law["effects"]) tcc["causes"].each do |c| return true unless law_causes.include?(token_map[c]["instantiates"]) end tcc["effects"].each do |e| return true unless law_effects.include?(token_map[e]["instantiates"]) end false end |
.delay_within_window(actual_delay, temporal) ⇒ Object
ALGORITHM E (Rule 20): does an observed delay fall within a covering law's temporal window? Inclusive at both ends (N12.5.2). 3.0.0: an ordinal delay compares to an ordinal window by integer tick count; an ordinal delay and a wall-clock window (or vice versa) are different dimensions and never fall within one another.
385 386 387 388 389 390 391 392 393 394 |
# File 'lib/causalontology/semantics.rb', line 385 def delay_within_window(actual_delay, temporal) return true if actual_delay.nil? || actual_delay.empty? || temporal.nil? || temporal.empty? # dimension mismatch: a tick delay is not within a wall-clock window return false if dimension(actual_delay["unit"]) != dimension(temporal["unit"]) observed = magnitude(actual_delay["duration"], actual_delay["unit"]) lo = magnitude(temporal["minimum_delay"], temporal["unit"]) hi = magnitude(temporal["maximum_delay"], temporal["unit"]) lo <= observed && observed <= hi end |
.dimension(unit) ⇒ Object
3.0.0: "ordinal" for a tick-like unit, else "wallclock".
58 59 60 |
# File 'lib/causalontology/semantics.rb', line 58 def dimension(unit) ORDINAL_UNITS.include?(unit) ? "ordinal" : "wallclock" end |
.endpoints_mixed(cro, occ_map) ⇒ Object
True iff causes or effects span more than one distinct stratum (surfaces mixed_stratal_endpoints, an invitation; N12.3.2).
335 336 337 338 339 340 341 |
# File 'lib/causalontology/semantics.rb', line 335 def endpoints_mixed(cro, occ_map) stratum_of = lambda { |occ_id| (occ_map[occ_id] || {})["stratum"] } cs = Set.new(cro["causes"].map { |c| stratum_of.call(c) }) es = Set.new(cro["effects"].map { |e| stratum_of.call(e) }) return false if cs.include?(nil) || es.include?(nil) cs.length > 1 || es.length > 1 end |
.has_cycle(edges) ⇒ Object
Rules 4 / 6.1: true iff a directed graph (node -> iterable of successors) has a cycle. Used for bridge graph, occurrent_subsumes, occurrent_part_of, and token mereology (part_of).
581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 |
# File 'lib/causalontology/semantics.rb', line 581 def has_cycle(edges) white = 0 grey = 1 black = 2 state = {} visit = nil visit = lambda do |node| state[node] = grey (edges[node] || []).each do |nxt| s = state.fetch(nxt, white) return true if s == grey return true if s == white && visit.call(nxt) end state[node] = black false end edges.keys.any? { |n| state.fetch(n, white) == white && visit.call(n) } end |
.hierarchy_consistent(parent, members, bridges = []) ⇒ Object
ALGORITHM B (amended Rule 7): "consistent" | "inconsistent" | "indeterminate", ACROSS STRATA via bridged reachability.
members: mapping from CRO identifier to CRO object for the mechanism entries. bridges: the store's bridges (empty -> 1.0.0 literal reachability, the degenerate case, N12.2.3).
281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 |
# File 'lib/causalontology/semantics.rb', line 281 def hierarchy_consistent(parent, members, bridges = []) mechanism = parent["mechanism"] || [] return "consistent" if mechanism.empty? # nothing claimed (N12.2.1) edges = {} mechanism.each do |mid| m = members[mid] return "indeterminate" if m.nil? # dangling; ignorance, not refutation m["causes"].each do |c| (edges[c] ||= Set.new).merge(m["effects"]) end end b_cause = {} parent["causes"].each { |c| b_cause[c] = bridge_closure(c, bridges) } b_effect = {} parent["effects"].each { |e| b_effect[e] = bridge_closure(e, bridges) } parent["causes"].each do |c| parent["effects"].each do |e| connected = b_cause[c].any? do |cp| b_effect[e].any? { |ep| path_exists(edges, cp, ep) } end return "inconsistent" unless connected end end "consistent" end |
.is_partial(cro) ⇒ Object
[partial, missing] - which optional CRO fields are unspecified.
158 159 160 161 |
# File 'lib/causalontology/semantics.rb', line 158 def is_partial(cro) missing = CRO_OPTIONAL_FIELDS.reject { |f| cro.key?(f) } [!missing.empty?, missing] end |
.kind_of_id(identifier) ⇒ Object
53 54 55 |
# File 'lib/causalontology/semantics.rb', line 53 def kind_of_id(identifier) Canonical::KIND_OF_PREFIX[identifier.split(":", 2)[0]] end |
.magnitude(value, unit) ⇒ Object
3.0.0: a comparable magnitude within ONE dimension: raw tick count for an ordinal unit, seconds for a wall-clock unit. Never mix dimensions.
64 65 66 67 68 |
# File 'lib/causalontology/semantics.rb', line 64 def magnitude(value, unit) return value if ORDINAL_UNITS.include?(unit) # a dimensionless tick count return 0 if unit == "instant" value * UNIT_SECONDS.fetch(unit) end |
.path_exists(edges, src, dst) ⇒ Object
262 263 264 265 266 267 268 269 270 271 272 273 |
# File 'lib/causalontology/semantics.rb', line 262 def path_exists(edges, src, dst) seen = Set.new stack = [src] until stack.empty? node = stack.pop return true if node == dst next if seen.include?(node) seen << node stack.concat((edges[node] || []).to_a) end false end |
.prediction_pairing_mismatch(error, predicted, observed) ⇒ Object
4.0.0 Rule 24: prediction-to-observation pairing. True iff the prediction error's observed token does not instantiate the occurrent its predicted_occurrence instantiates (surfaces pairing_mismatch). An ABSENT observed is never a mismatch - it means the predicted occurrence was not fulfilled by any recorded occurrence.
560 561 562 563 |
# File 'lib/causalontology/semantics.rb', line 560 def prediction_pairing_mismatch(error, predicted, observed) return false if error["observed"].nil? || observed.nil? observed["instantiates"] != predicted["instantiates"] end |
.refinement_valid(child, parent) ⇒ Object
Rule 3: [ok, reason] - is child a valid refinement of parent?
209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 |
# File 'lib/causalontology/semantics.rb', line 209 def refinement_valid(child, parent) if child["refines"] != parent["id"] return [false, "child does not name the parent in refines"] end if Set.new(child["causes"]) != Set.new(parent["causes"]) || Set.new(child["effects"]) != Set.new(parent["effects"]) return [false, "a refinement must keep the parent's causes and effects"] end added = 0 CRO_OPTIONAL_FIELDS.each do |field| if parent.key?(field) if child[field] != parent[field] return [false, "a refinement may not change a field the " \ "parent specified; this is a rival claim"] end elsif child.key?(field) added += 1 end end if added == 0 return [false, "a refinement must add at least one unspecified field"] end [true, "valid refinement"] end |
.retrocausal(tcc, token_map) ⇒ Object
Rule 21: true iff any cause token starts after any effect token (HARD; retrocausal_claim). RFC 3339 UTC "Z" strings compare lexicographically.
567 568 569 570 571 572 573 574 575 576 |
# File 'lib/causalontology/semantics.rb', line 567 def retrocausal(tcc, token_map) tcc["causes"].each do |c| cstart = token_map[c]["interval"]["start"] tcc["effects"].each do |e| estart = token_map[e]["interval"]["start"] return true if cstart > estart end end false end |
.seam_home(seam, occ_map, stratum_map) ⇒ Object
THE HOME RULE (3.0.0): a Cross Stratal Seam belongs to the COARSEST stratum it touches - the endpoint of the greater ordinal. Returns that stratum's identifier (nil if an endpoint is unstratified). A layer-to-stratum binding places and checks the seam by this rule.
475 476 477 478 479 480 481 482 483 484 |
# File 'lib/causalontology/semantics.rb', line 475 def seam_home(seam, occ_map, stratum_map) src_s = (occ_map[seam["source"]] || {})["stratum"] tgt_s = (occ_map[seam["target"]] || {})["stratum"] return nil if src_s.nil? || tgt_s.nil? if stratum_map[src_s]["ordinal"] >= stratum_map[tgt_s]["ordinal"] src_s else tgt_s end end |
.seam_wellformed(seam, occ_map, stratum_map) ⇒ Object
3.0.0 Rule 22 / Algorithm F: Cross Stratal Seam well-formedness. [ok, reason]. All of (a)-(g) must hold, else malformed_seam. A seam is a MANAGED jump across NON-ADJACENT strata; when it DRAWS a chain, the chain must be an adjacent-stratum path spanning the two endpoints' strata.
423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 |
# File 'lib/causalontology/semantics.rb', line 423 def seam_wellformed(seam, occ_map, stratum_map) src_s = (occ_map[seam["source"]] || {})["stratum"] tgt_s = (occ_map[seam["target"]] || {})["stratum"] if src_s.nil? || tgt_s.nil? return [false, "malformed_seam: an endpoint has no stratum (a)"] end if stratum_map[src_s]["scheme"] != stratum_map[tgt_s]["scheme"] return [false, "malformed_seam: endpoints differ in scheme (b)"] end so = stratum_map[src_s]["ordinal"] to_ = stratum_map[tgt_s]["ordinal"] if (so - to_).abs <= 1 return [false, "malformed_seam: endpoints are adjacent or co-stratal; " \ "a seam is for NON-adjacent strata (c)"] end chain = seam["chain"] unless chain.nil? if seam["mechanism_status"] == "absent" return [false, "malformed_seam: a drawn chain contradicts " \ "mechanism_status 'absent' (d)"] end lo = [so, to_].min hi = [so, to_].max ords = [] chain.each do |oid| st = (occ_map[oid] || {})["stratum"] if st.nil? return [false, "malformed_seam: a chain member has no stratum (e)"] end if stratum_map[st]["scheme"] != stratum_map[src_s]["scheme"] return [false, "malformed_seam: a chain member differs in scheme (e)"] end ords << stratum_map[st]["ordinal"] end unless ords.all? { |o| lo < o && o < hi } return [false, "malformed_seam: a chain member is not at an " \ "INTERVENING stratum, strictly between the endpoints (f)"] end diffs = (0...(ords.length - 1)).map { |i| ords[i + 1] - ords[i] } if !diffs.empty? && !(diffs.all? { |d| d > 0 } || diffs.all? { |d| d < 0 }) return [false, "malformed_seam: chain is not strictly monotone from " \ "one endpoint toward the other (g)"] end end [true, "well-formed cross_stratal_seam"] end |
.skip_gaps(cro, classification) ⇒ Object
ALGORITHM D (Rule 16): the gaps a Causal Relation Object surfaces for the skip decision. THE ASYMMETRY (clause 3) is the whole point of the field and is implemented exactly.
346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 |
# File 'lib/causalontology/semantics.rb', line 346 def skip_gaps(cro, classification) gaps = [] has_mech = !(cro["mechanism"] || []).empty? if cro["skips"] == true && has_mech gaps << "contradictory_skip" # HARD return gaps end if cro["skips"] == true && !["skipping", "unclassifiable"].include?(classification) gaps << "vacuous_skip" # invitation end if classification == "skipping" && !has_mech if cro["skips"] == true # NOTHING: absence is a finding else gaps << "incomplete_mechanism" # invitation end end gaps end |
.state_gaps(state, quality) ⇒ Object
Rule 19 / N5.3.1-2: the HARD gaps a state assertion surfaces against its quality: value_type_mismatch and/or unit_mismatch.
523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 |
# File 'lib/causalontology/semantics.rb', line 523 def state_gaps(state, quality) gaps = [] dt = quality["datatype"] v = state["value"] || {} shape = if v.key?("quantity") then "quantity" elsif v.key?("categorical") then "categorical" elsif v.key?("boolean") then "boolean" end if shape != dt gaps << "value_type_mismatch" elsif dt == "quantity" && v["unit"] != quality["unit"] gaps << "unit_mismatch" end gaps end |
.to_seconds(duration, unit) ⇒ Object
ALGORITHM E helper: normalize a delay to seconds by the fixed table. 3.0.0: an ordinal ('ticks') unit is dimensionless and has NO wall-clock mapping - converting one to seconds is a category error and is refused.
370 371 372 373 374 375 376 377 378 |
# File 'lib/causalontology/semantics.rb', line 370 def to_seconds(duration, unit) if ORDINAL_UNITS.include?(unit) raise ArgumentError, "'#{unit}' is an ordinal (dimensionless) unit and has no " \ "wall-clock seconds mapping" end return 0 if unit == "instant" duration * UNIT_SECONDS.fetch(unit) end |
.validate_semantics(obj, kind = nil) ⇒ Object
[ok, reasons] - the locally checkable semantic rules.
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 |
# File 'lib/causalontology/semantics.rb', line 71 def validate_semantics(obj, kind = nil) kind ||= Canonical.infer_kind(obj) errors = [] if kind == "causal_relation_object" t = obj["temporal"] if !t.nil? && !t["minimum_delay"].nil? && !t["maximum_delay"].nil? && t["minimum_delay"] > t["maximum_delay"] errors << "minimum_delay must be <= maximum_delay" end oid = obj["id"] if oid && (obj["mechanism"] || []).include?(oid) errors << "mechanism must be acyclic " \ "(a Causal Relation Object may not contain itself)" end if oid && obj["refines"] == oid errors << "refines must be acyclic" end # Rule 16, clause 1 (contradictory_skip): a HARD, locally-decidable # contradiction between skips:true and a non-empty mechanism. if obj["skips"] == true && obj["mechanism"] && !obj["mechanism"].empty? errors << "contradictory_skip: skips is true but a mechanism " \ "is present" end end if kind == "enrichment" field = obj["field"] about = obj["about"] || "" entry = obj["entry"] spec = ENRICHMENT_FIELDS[field] if spec legal_kinds, shape = spec about_kind = kind_of_id(about) if about_kind && !legal_kinds.include?(about_kind) errors << "#{field} is not a legal field for a #{about_kind} (rule 12)" end if shape == "alias" unless entry.is_a?(Hash) && entry.key?("lang") && entry.key?("text") errors << "an aliases entry must be a language-tagged text object" end else unless entry.is_a?(String) && entry.start_with?(shape + ":") errors << "a #{field} entry must be a #{shape}: identifier" end end end end # 3.0.0 Rule 22, local clause: a Cross Stratal Seam that DRAWS a chain has, # by drawing it, a modelled intervening mechanism - so mechanism_status # 'absent' contradicts a present chain (the honest-ignorance distinction # must stay honest). The stratal well-formedness (non-adjacency, adjacency # of chain steps, scheme, the home rule) needs the strata map and lives in # seam_wellformed, exactly as bridge well-formedness does. if kind == "cross_stratal_seam" if !obj["chain"].nil? && obj["mechanism_status"] == "absent" errors << "contradictory_seam: a drawn chain cannot carry " \ "mechanism_status 'absent' (a drawn mechanism is not absent)" end end # 4.0.0 Rule 24, local clause: a predicted_occurrence's interval carries # exactly ONE temporal dimension - a wall-clock start (optional end) or an # ordinal start_tick (optional end_tick), never both and never neither. # Per Rule 23 the two dimensions never compare. The pairing check of a # prediction_error against its predicted_occurrence and its observed # token_occurrence needs those objects and lives in # prediction_pairing_mismatch, exactly as covering_law_mismatch does. if kind == "predicted_occurrence" iv = obj["interval"] || {} wall = iv.key?("start") tick = iv.key?("start_tick") if wall && tick errors << "dimension_conflict: a predicted interval must " \ "carry exactly one temporal dimension, not a " \ "wall-clock start AND an ordinal start_tick" end if !wall && !tick errors << "missing_dimension: a predicted interval must " \ "carry a wall-clock start or an ordinal start_tick" end end [errors.empty?, errors] end |
.window_overlap(a, b) ⇒ Object
174 175 176 177 178 179 180 181 182 183 184 185 |
# File 'lib/causalontology/semantics.rb', line 174 def window_overlap(a, b) ta = a["temporal"] tb = b["temporal"] return true if ta.nil? || tb.nil? # either absent counts as overlapping # 3.0.0: an ordinal window and a wall-clock window never overlap return false if dimension(ta["unit"]) != dimension(tb["unit"]) lo_a = magnitude(ta["minimum_delay"], ta["unit"]) hi_a = magnitude(ta["maximum_delay"], ta["unit"]) lo_b = magnitude(tb["minimum_delay"], tb["unit"]) hi_b = magnitude(tb["maximum_delay"], tb["unit"]) lo_a <= hi_b && lo_b <= hi_a end |