Package jazzparser :: Package formalisms :: Package music_halfspan :: Module rules
[hide private]
[frames] | no frames]

Source Code for Module jazzparser.formalisms.music_halfspan.rules

  1  """Grammar rules module for the music_halfspan formalism. 
  2   
  3  Grammatical rules for the keyspan formalism. A lot of this is standard  
  4  CCG, with a few special additions. 
  5   
  6  """ 
  7  """ 
  8  ============================== License ======================================== 
  9   Copyright (C) 2008, 2010-12 University of Edinburgh, Mark Granroth-Wilding 
 10    
 11   This file is part of The Jazz Parser. 
 12    
 13   The Jazz Parser is free software: you can redistribute it and/or modify 
 14   it under the terms of the GNU General Public License as published by 
 15   the Free Software Foundation, either version 3 of the License, or 
 16   (at your option) any later version. 
 17    
 18   The Jazz Parser is distributed in the hope that it will be useful, 
 19   but WITHOUT ANY WARRANTY; without even the implied warranty of 
 20   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 21   GNU General Public License for more details. 
 22    
 23   You should have received a copy of the GNU General Public License 
 24   along with The Jazz Parser.  If not, see <http://www.gnu.org/licenses/>. 
 25   
 26  ============================ End license ====================================== 
 27   
 28  """ 
 29  __author__ = "Mark Granroth-Wilding <mark.granroth-wilding@ed.ac.uk>"  
 30   
 31  import logging, copy 
 32  from jazzparser.formalisms.base.rules import ApplicationRuleBase, CompositionRuleBase, \ 
 33                          Rule 
 34  from jazzparser.formalisms.base.semantics.temporal import temporal_rule_apply 
 35  from jazzparser.formalisms.base.semantics.lambdacalc import distinguish_variables, \ 
 36                          next_unused_variable 
 37  from .syntax import AtomicCategory, Sign 
 38  from .semantics import concatenate, Variable, LambdaAbstraction, \ 
 39                          FunctionApplication, Semantics, Now, \ 
 40                          EnharmonicCoordinate, Coordination 
 41  from jazzparser.parsers import RuleApplicationError 
 42  from jazzparser import settings 
 43  from jazzparser.utils.tonalspace import coordinate_to_et_2d 
 44   
 45  # Get the logger from the logging system 
 46  logger = logging.getLogger("main_logger") 
47 48 49 -class ApplicationRule(ApplicationRuleBase):
50 - def __init__(self, *args, **kwargs):
51 from . import Formalism 52 super(ApplicationRule, self).__init__(Formalism, *args, **kwargs)
53
54 - def apply_rule(self, cat_list, proc_sems=None, *args, **kwargs):
55 """ 56 For halfspan, the definition of the application rule is very 57 important. It's not the standard CCG application. Crucially, 58 it requires the argument part of the slash category to match 59 the nearest end of the argument category. 60 61 """ 62 ################## Check the rule is valid 63 # Must be applied to 2 args 64 if len(cat_list) != 2: 65 return None 66 67 # Functor arg must be a slash category in the right direction 68 if self.forward: 69 # X/Y Y => X 70 functor = cat_list[0] 71 argument = cat_list[1] 72 else: 73 # Y X\Y => X 74 functor = cat_list[1] 75 argument = cat_list[0] 76 77 # Check correct cat type 78 if not self.formalism.Syntax.is_atomic_category(argument.category): 79 return None 80 if not self.formalism.Syntax.is_complex_category(functor.category): 81 return None 82 # Check right slash direction 83 if functor.category.slash.forward != self.forward: 84 return None 85 86 if self.forward: 87 near_argument = argument.category.from_half 88 else: 89 near_argument = argument.category.to_half 90 91 # Here's the crucial check: 92 # in W/X Y-Z, X must match Y 93 # in W-V Y\Z, Z must match V 94 if not functor.category.argument.matches(near_argument): 95 return None 96 97 ################## Checks passed: apply the rule 98 # Apply the unification substitutions 99 # Build the new category out of the result part of the slash 100 # category and the far part of the argument 101 functor_cat = functor.category.copy() 102 argument_cat = argument.category.copy() 103 104 if self.forward: 105 category = AtomicCategory(functor_cat.result, argument_cat.to_half) 106 else: 107 category = AtomicCategory(argument_cat.from_half, functor_cat.result) 108 109 semantics = self.apply_rule_semantics(cat_list, proc_sems=proc_sems)[0] 110 result = self.formalism.Syntax.Sign(category, semantics) 111 112 if settings.WARN_ABOUT_FREE_VARS: 113 free_vars = semantics.lf.get_unbound_variables() 114 if free_vars: 115 logger.warn("Found free variables after application: %s in %s" % (",".join(["%s" % var for var in free_vars]), semantics)) 116 117 return [result]
118 119 @temporal_rule_apply(semantics_only=True)
120 - def apply_rule_semantics(self, cat_list, proc_sems=None):
121 """ 122 Provides the semantic part of rule application separately from the 123 syntactic part. 124 125 @see: L{jazzparser.formalisms.base.rules.Rule.apply_rule_semantics} 126 127 """ 128 # Assume all syntactic checks have succeeded and do the semantic processing 129 if self.forward: 130 # X/Y Y => X 131 functor = cat_list[0] 132 argument = cat_list[1] 133 else: 134 # Y X\Y => X 135 functor = cat_list[1] 136 argument = cat_list[0] 137 138 # Combine semantics by function application 139 new_functor = functor.semantics.copy() 140 new_argument = argument.semantics.copy() 141 # Run any additional processing on the semantics of the inputs 142 if proc_sems is not None: 143 proc_sems(new_functor, new_argument) 144 145 semantics = self.formalism.Semantics.apply(new_functor, new_argument, grammar=self.grammar) 146 147 if settings.WARN_ABOUT_FREE_VARS: 148 free_vars = semantics.lf.get_unbound_variables() 149 if free_vars: 150 logger.warn("Found free variables after application: %s in %s" % (",".join(["%s" % var for var in free_vars]), semantics)) 151 return [semantics]
152
153 154 -class CompositionRule(CompositionRuleBase):
155 - def __init__(self, *args, **kwargs):
156 from . import Formalism 157 super(CompositionRule, self).__init__(Formalism, *args, **kwargs)
158
159 - def apply_rule(self, cat_list, proc_sems=None, *args, **kwargs):
160 """ 161 Unlike application, composition could use the standard CCG 162 definition, but here we redefine it. This allows us to handle 163 multiple functions on arguments not by using unification, but 164 just by calling C{matches}. 165 166 """ 167 ################## Check the rule is valid 168 # Can only operate on 2 categories 169 if len(cat_list) != 2: 170 return None 171 172 first = cat_list[0] 173 second = cat_list[1] 174 175 if not self.formalism.Syntax.is_complex_category(first.category) or \ 176 not self.formalism.Syntax.is_complex_category(second.category): 177 return None 178 179 if self.harmonic: 180 if first.category.slash.forward != self.forward or \ 181 second.category.slash.forward != self.forward: 182 return None 183 else: 184 if not first.category.slash.forward or \ 185 second.category.slash.forward: 186 return None 187 188 # Extract the right bits according to whether it's forward or backward 189 if self.forward: 190 middle_arg = first.category.argument 191 middle_res = second.category.result 192 else: 193 middle_arg = second.category.argument 194 middle_res = first.category.result 195 196 if not middle_arg.matches(middle_res): 197 return None 198 199 ################## Checks passed: apply the rule 200 first_cat = first.category.copy() 201 second_cat = second.category.copy() 202 # Extract the right bits according to whether it's forward or backward. 203 # This bit is the same for harmonic and crossing. 204 if self.forward: 205 result = first_cat.result 206 argument = second_cat.argument 207 else: 208 result = second_cat.result 209 argument = first_cat.argument 210 211 # Harmonic: forward slashed result for forward slashed rule. 212 # Crossing: backward slashed result for forward slashed rule. 213 slash = self.formalism.Syntax.Slash(self.forward == self.harmonic) 214 215 # Set the slash modality 216 if first.category.slash.modality == '': 217 slash.modality = second.category.slash.modality 218 else: 219 # The first slash is cadential. If the second is cadential, 220 # they agree, so the result is cadential. If the second is empty, 221 # the result is still cadential, since the first one wins. 222 slash.modality = first.category.slash.modality 223 224 category = self.formalism.Syntax.ComplexCategory( 225 result, \ 226 slash, \ 227 argument) 228 229 semantics = self.apply_rule_semantics(cat_list, proc_sems=proc_sems)[0] 230 result = self.formalism.Syntax.Sign(category, semantics) 231 232 return [result]
233 234 @temporal_rule_apply(semantics_only=True)
235 - def apply_rule_semantics(self, cat_list, proc_sems=None):
236 """ 237 Provides the semantic part of rule application separately from the 238 syntactic part. 239 240 @see: L{jazzparser.formalisms.base.rules.Rule.apply_rule_semantics} 241 242 """ 243 # Assume all syntactic checks have succeeded and do the semantic processing 244 if self.forward: 245 fun_f = cat_list[0].semantics.copy() 246 fun_g = cat_list[1].semantics.copy() 247 else: 248 fun_g = cat_list[0].semantics.copy() 249 fun_f = cat_list[1].semantics.copy() 250 251 # Make sure we don't confuse similarly-named variables 252 # Don't think we should need to do this, because fun app should take care of it 253 #from jazzparser.formalisms.base.semantics.lambdacalc import distinguish_variables 254 #distinguish_variables(fun_f, fun_g) 255 256 if proc_sems is not None: 257 proc_sems(fun_f, fun_g) 258 259 semantics = self.formalism.Semantics.compose(fun_f, fun_g) 260 261 # Make sure semantics is in BNF 262 semantics.beta_reduce(grammar=self.grammar) 263 264 if settings.WARN_ABOUT_FREE_VARS: 265 free_vars = semantics.lf.get_unbound_variables() 266 if free_vars: 267 logger.warn("Found free variables after composition: %s in %s" % (",".join(["%s" % var for var in free_vars]), semantics)) 268 return [semantics]
269
270 271 -class DevelopmentRule(Rule):
272 """ 273 The development rule strings together sequences of resolved cadences. 274 275 This used to be called "continuation" and was renamed long ago, but 276 the old name persisted in the keyspan implementation. 277 278 """
279 - def __init__(self, *args, **kwargs):
280 from . import Formalism 281 super(DevelopmentRule, self).__init__(Formalism, *args, **kwargs) 282 self.name = "<dev>" 283 self.internal_name = "dev" 284 self.readable_rule = "W-X Y-Z =>dev W-Z" 285 286 self.arity = 2
287
288 - def apply_rule(self, cat_list, proc_sems=None):
289 from . import Formalism 290 ################## Check the rule is valid 291 # Only 2 categories at a time 292 if len(cat_list) != 2: 293 return None 294 295 # Can only apply to atomic categories 296 if not Formalism.Syntax.is_atomic_category(cat_list[0].category) or \ 297 not Formalism.Syntax.is_atomic_category(cat_list[1].category): 298 return None 299 300 ################## Checks passed: apply the rule 301 # Build the resulting syntactic category 302 first_cat = cat_list[0].category 303 second_cat = cat_list[1].category 304 305 from .syntax import AtomicCategory 306 result_cat = AtomicCategory( 307 first_cat.from_half.copy(), 308 second_cat.to_half.copy()) 309 310 semantics = self.apply_rule_semantics(cat_list, proc_sems=proc_sems)[0] 311 312 return [Sign(result_cat, semantics)]
313 314 @temporal_rule_apply(semantics_only=True)
315 - def apply_rule_semantics(self, cat_list, proc_sems=None):
316 """ 317 Provides the semantic part of rule application separately from the 318 syntactic part. 319 320 @see: L{jazzparser.formalisms.base.rules.Rule.apply_rule_semantics} 321 322 """ 323 # Assume all syntactic checks have succeeded and do the semantic processing 324 sem0 = cat_list[0].semantics.copy() 325 sem1 = cat_list[1].semantics.copy() 326 327 if proc_sems is not None: 328 proc_sems(sem0, sem1) 329 330 # The two inputs should be lists: just concatenate them 331 semantics = concatenate(sem0, sem1) 332 return [semantics]
333
334 -class CoordinationRule(Rule):
335 """ 336 The coordination rule allows partial cadences to combine and share 337 a resolution. 338 339 """
340 - def __init__(self, *args, **kwargs):
341 from . import Formalism 342 super(CoordinationRule, self).__init__(Formalism, *args, **kwargs) 343 self.name = "<&>" 344 self.internal_name = "coord" 345 self.readable_rule = "X/Y Z/Y =>& X/Y" 346 347 self.arity = 2
348
349 - def apply_rule(self, cat_list, proc_sems=None):
350 from . import Formalism 351 ################## Check the rule is valid 352 # Only 2 categories at a time 353 if len(cat_list) != 2: 354 return None 355 cat0, cat1 = cat_list[0], cat_list[1] 356 357 # Can only apply to cadential slash categories 358 if not Formalism.Syntax.is_complex_category(cat0.category) or \ 359 not Formalism.Syntax.is_complex_category(cat1.category): 360 return None 361 362 # Both categories must have a cadence modality 363 if not (cat0.category.slash.modality == cat1.category.slash.modality == 'c'): 364 return None 365 366 arg0, arg1 = cat0.category.argument, cat1.category.argument 367 # Both categories must have an identical argument (except for 368 # functions, see below) 369 if not (arg0.root == arg1.root): 370 return None 371 372 # The categories must at least share some function on their arguments 373 if arg0.functions.isdisjoint(arg1.functions): 374 return None 375 376 # They must have the same function on their result 377 if not (cat0.category.result.functions == cat1.category.result.functions): 378 return None 379 380 ################## Checks passed: apply the rule 381 # The result is syntactically the same as the first input 382 new_cat = cat0.category.copy() 383 # Restrict the argument to functions allowed by both the inputs' arguments 384 new_cat.argument.functions = arg0.functions & arg1.functions 385 386 semantics = self.apply_rule_semantics(cat_list, proc_sems=proc_sems)[0] 387 result = self.formalism.Syntax.Sign(new_cat, semantics) 388 389 return [result]
390 391 @temporal_rule_apply(semantics_only=True)
392 - def apply_rule_semantics(self, cat_list, proc_sems=None):
393 """ 394 Provides the semantic part of rule application separately from the 395 syntactic part. 396 397 @see: L{jazzparser.formalisms.base.rules.Rule.apply_rule_semantics} 398 399 """ 400 # Assume all syntactic checks have succeeded and do the semantic processing 401 cad0 = cat_list[0].semantics.lf.copy() 402 cad1 = cat_list[1].semantics.lf.copy() 403 404 if proc_sems is not None: 405 proc_sems(cad0, cad1) 406 407 # Use the special coordination logical operator to combine these 408 semantics = Semantics( 409 Coordination([cad0, cad1])) 410 semantics.beta_reduce() 411 412 if settings.WARN_ABOUT_FREE_VARS: 413 free_vars = semantics.lf.get_unbound_variables() 414 if free_vars: 415 logger.warn("Found free variables after coordination: %s in %s" % (",".join(["%s" % var for var in free_vars]), semantics)) 416 return [semantics]
417
418 419 -class TonicRepetitionRule(Rule):
420 """ 421 A special unary rule for expanding the lexicon to add tonic 422 repetition categories, generated from the tonic categories already 423 in the lexicon. 424 425 This doesn't get used during parsing, but only to generate the 426 full lexicon when it's loaded up. 427 428 It will expand any tonic category: X[T] => X[T]/X[T] : \\x.x 429 430 """
431 - def __init__(self, *args, **kwargs):
432 from . import Formalism 433 super(TonicRepetitionRule, self).__init__(Formalism, *args, **kwargs) 434 self.name = "<rep>" 435 self.internal_name = "rep" 436 self.readable_rule = "X[T] =>rep X[T]/X[T]" 437 self.arity = 1
438
439 - def apply_rule(self, cat_list, proc_sems=None):
440 from . import Formalism 441 ################## Check the rule is valid 442 # Unary rule 443 if len(cat_list) != 1: 444 return None 445 sign = cat_list[0] 446 cat = sign.category 447 448 if not Formalism.Syntax.is_atomic_category(cat): 449 return None 450 451 # Lexical categories will only ever be specified as half 452 # categories, since their start and end will always be equal. 453 # Check that this is only being applied to such categories 454 if cat.from_half != cat.to_half: 455 return None 456 457 ################## Checks passed: apply the rule 458 # We need two half categories: both are the same as the halves 459 # of the input cat 460 new_res = cat.from_half.copy() 461 new_arg = cat.from_half.copy() 462 new_cat = Formalism.Syntax.ComplexCategory( 463 new_res, 464 Formalism.Syntax.Slash(dir=True), 465 new_arg) 466 467 # Semantics for every result should be just \x.x 468 semantics = Semantics( 469 LambdaAbstraction( 470 Variable("x"), 471 FunctionApplication( 472 Now(), 473 Variable("x") 474 ) 475 )) 476 477 result = Formalism.Syntax.Sign(new_cat, semantics) 478 return [result]
479
480 481 -class CadenceRepetitionRule(Rule):
482 """ 483 A special unary rule for expanding the lexicon to add dominant 484 or subdominant repetition categories for all the substitutions, 485 generated from the cadential categories already in the lexicon. 486 487 This doesn't get used during parsing, but only to generate the 488 full lexicon when it's loaded up. 489 490 It will expand any cadence category: X[f]/c Y => X[f]/X[f] : \\x.x 491 492 """
493 - def __init__(self, *args, **kwargs):
494 from . import Formalism 495 super(CadenceRepetitionRule, self).__init__(Formalism, *args, **kwargs) 496 self.name = "<crep>" 497 self.internal_name = "crep" 498 self.readable_rule = "X[f]/c Y =>crep X[f]/X[f]" 499 self.arity = 1
500
501 - def apply_rule(self, cat_list, proc_sems=None):
502 from . import Formalism 503 ################## Check the rule is valid 504 # Unary rule 505 if len(cat_list) != 1: 506 return None 507 sign = cat_list[0] 508 cat = sign.category 509 510 if not Formalism.Syntax.is_complex_category(cat): 511 return None 512 513 # Slash modality must be cadential 514 if cat.slash.modality != "c": 515 return None 516 517 # Double-check that the function on the result is dominant 518 # or subdominant (it should be, if the modality is cadential) 519 if cat.result.function not in ["D","S"]: 520 return None 521 522 ################## Checks passed: apply the rule 523 # We're only interested in the result part (because that 524 # gives the syntactic starting point) 525 new_res = cat.result.copy() 526 new_arg = cat.result.copy() 527 # The output has a non-cadential slash, because these things 528 # don't constitute cadences on their own - only once they've 529 # combined with another cadential cat 530 new_cat = Formalism.Syntax.ComplexCategory( 531 new_res, 532 Formalism.Syntax.Slash(dir=True), 533 new_arg) 534 535 # Semantics for every result should be just \x.x 536 semantics = Semantics( 537 LambdaAbstraction( 538 Variable("x"), 539 FunctionApplication( 540 Now(), 541 Variable("x") 542 ) 543 )) 544 545 result = Formalism.Syntax.Sign(new_cat, semantics) 546 return [result]
547