In [39]:
import forge
from puzzle.puzzlepedia import puzzlepedia

puzzle = puzzlepedia.parse("""
model.disable_constraints()
model.disable_inference()

woman in {wA, wB, wC, wD, wE, wF, wG}
concealer in {bigger, burning, bye, enthusiastic, makeup, married, shapely}
(kinds, colors) in ({eye, lip}, {CA, ER, EU, FI, GR, HE, ID, LE, SH, SP, SE, TY, TH, VA})

eyes = [eyeCA, eyeER, eyeEU, eyeFI, eyeGR, eyeHE, eyeID, eyeLE, eyeSH, eyeSP, eyeSE, eyeVA]
lips = [lipCA, lipER, lipEU, lipFI, lipGR, lipHE, lipID, lipLE, lipSH, lipSP, lipSE, lipVA, lipTY, lipTH]

# Each woman has one of each.
for w in woman:
  sum(w[c] for c in concealer) == 1
  sum(w[e] for e in eyes) == 1
  sum(w[l] for l in lips) == 1

# Each concealer is used once.
for c in concealer:
  sum(c[w] for w in woman) == 1

# Each color is used once.
for a, b in zip(eyes, lips):
  sum(w[a] + w[b] for w in woman) == 1

# 5: Someone has matching lipstick and eyeshadow first letter
# 6: Number of S lipsticks == number of T lipsticks
#12: None of the eyeshadows start with same letter
# E E S S S T T
# Lip: E E? S S T T
# Eye:   E? S
# Conclusion: Lip has 2 Ss, 2 Ts.
sum(any(w[c] for w in woman) for c in [lipSH, lipSP, lipSE]) == 2
sum(any(w[c] for w in woman) for c in [lipTY, lipTH]) == 2
for w in woman:
  w != eyeTY
  w != eyeTH
#11: FI + ER combo exists
# Conclusion: Matching letter is an S, not E.

def color(dim, c):
  return dim['eye' + c] | dim['lip' + c]

def color_vector(c):
  return sum(color(w, c) * i for i, w in zip(range(8), woman))

def matched_vector(dim):
  return sum(w[dim] * i for i, w in zip(range(8), woman))

def matching_color(w):
  return (
    (color(w, 'ER') + color(w, 'EU') == 2) +
    ((color(w, 'SH') + color(w, 'SP') + color(w, 'SE')) == 2) +
    (color(w, 'TY') + color(w, 'TH') == 2)
  )
# NB: Matching color must be an S. See conclusion above.
def matching_color(w):
  return (color(w, 'SH') + color(w, 'SP') + color(w, 'SE')) == 2

def concealer_vector(c):
  return sum(w[c] * i for i, w in zip(range(8), woman))

#1: LGTM.
wC.lipEU or wC.lipSH

#2: LGTM.
wC != married
wG != married

#3: LGTM.
#(color(wE, 'TY') and wE.enthusiastic) or (color(wF, 'TY') and wF.enthusiastic)
# NB: Ts are lip colors; see above.
(wE.lipTY and wE.enthusiastic) or (wF.lipTY and wF.enthusiastic)

#4: LGTM.
any(bigger[w] and (eyeSE[w] or eyeGR[w]) for w in woman)

#5: LGTM.
# Someone (not wC) wore first-letter-matching lip & eye
#not matching_color(wC)
matching_women = [wA, wB, wD, wE, wF, wG]
# Duplicates: ER, EU; SH, SP, SE; TY, TH
any(matching_color(w) for w in matching_women)

#6: LGTM.
# NB: Implemented via conclusions above.

#7: LGTM.
if any(lipSP[w] for w in woman):
  color(wD, 'CA')

#8: LGTM.
#8a: Find bbq eyeshadow, shapely lipstick and assign GR xor ID.
# Eligible determined via GR | ID eligibility.
# B excluded by definition.
# C excluded because #1 says lip is either EU or SH (not GR).
# G excluded due to #15.
shapely_eligible = [wA, wD, wE, wF]
shapely_ineligible = [wB, wC, wG]
for w in shapely_eligible:
  if w.shapely: (w.lipGR and wB.eyeID) or (w.lipID and wb.eyeGR)
#8b: (These two are different women.)
for w in shapely_ineligible:
  w != shapely

#9: LGTM.
#9: TH first letter > EU first letter
matched_vector(lipTH) > color_vector('EU')

#10: TODO.
# CA woman and VA woman have matching concealers.
# Matching concealers: bigger, burning, bye; makeup, married
b_concealers = [bigger, burning, bye]
ca_is_b = any(color(w, 'CA') and any(w[c] for c in b_concealers) for w in woman)
va_is_b = any(color(w, 'VA') and any(w[c] for c in b_concealers) for w in woman)
ca_is_b == va_is_b
m_concealers = [makeup, married]
ca_is_m = any(color(w, 'CA') and any(w[c] for c in m_concealers) for w in woman)
va_is_m = any(color(w, 'VA') and any(w[c] for c in m_concealers) for w in woman)
ca_is_m == va_is_m

#11: LGTM.
#11: FI/ER woman exists
any((w.eyeFI and w.lipER) or (w.eyeER and w.lipFI) for w in woman)

#12: LGTM.
#12: None of the eyeshadows start with the same letter.
eye_letters = [
  [eyeER, eyeEU],
  [eyeSH, eyeSP, eyeSE],
]
# None of the eyeshadow colors use T; see deduction above.
#   [eyeTY, eyeTH],
# Duplicates: eyeER, eyeEU; eyeSH, eyeSP, eyeSE; eyeTY, eyeTH
for eye_group in eye_letters:
  sum(any(e[w] for e in eye_group) for w in woman) <= 1


#13: Exactly two women used eyeshadow, concealer, or lipstick that started with same name.
# wA: None
# wB: bigger, burning, bye
# wC: eyeCA, lipCA
# wD: None
# wE: enthusiastic, eyeER, lipER, eyeEU, lipEU
# wF: eyeFI, lipFI
# wG: eyeGR, lipGR
sum([
  any([wB.bigger, wB.burning, wB.bye]),
  any([wC.eyeCA, wC.lipCA]),
  any([wE.enthusiastic, wE.eyeER, wE.lipER, wE.eyeEU, wE.lipEU]),
  any([wF.eyeFI, wF.lipFI]),
  any([wG.eyeGR, wF.lipGR]),
]) == 2

#14: LGTM.
wF != eyeHE
wF != lipHE
wF != eyeER
wF != lipER

#15: LGTM.
wG == lipLE

#16: LGTM.
# wX.makeup's name is 2+ before burning
matched_vector(burning) - matched_vector(makeup) > 1


model.grid()
#with init:
#  debug(str(model))
""")


Default constraints disabled
Default inference disabled

 *	bigger	burning	bye	enthusiastic	makeup	married	shapely		eyeCA	eyeER	eyeEU	eyeFI	eyeGR	eyeHE	eyeID	eyeLE	eyeSH	eyeSP	eyeSE	eyeTY	eyeTH	eyeVA	lipCA	lipER	lipEU	lipFI	lipGR	lipHE	lipID	lipLE	lipSH	lipSP	lipSE	lipTY	lipTH	lipVA	
wA	0	0	0	0	0	0	1		0	0	1	0	0	0	0	0	0	0	0	0	0	0	0	0	0	0	0	0	1	0	0	0	0	0	0	0	
wB	1	0	0	0	0	0	0		0	0	0	0	1	0	0	0	0	0	0	0	0	0	0	0	0	0	0	0	0	0	0	0	0	0	1	0	
wC	0	0	1	0	0	0	0		0	0	0	0	0	0	0	0	0	0	0	0	0	1	0	0	0	0	0	0	0	0	1	0	0	0	0	0	
wD	0	0	0	0	1	0	0		0	0	0	1	0	0	0	0	0	0	0	0	0	0	0	1	0	0	0	0	0	0	0	0	0	0	0	0	
wE	0	0	0	1	0	0	0		0	0	0	0	0	1	0	0	0	0	0	0	0	0	0	0	0	0	0	0	0	0	0	0	0	1	0	0	
wF	0	0	0	0	0	1	0		0	0	0	0	0	0	0	0	0	1	0	0	0	0	0	0	0	0	0	0	0	0	0	0	1	0	0	0	
wG	0	1	0	0	0	0	0		1	0	0	0	0	0	0	0	0	0	0	0	0	0	0	0	0	0	0	0	0	1	0	0	0	0	0	0	

eyeCA	0	0	0	0	0	0	0	
eyeER	0	0	0	0	0	0	0	
eyeEU	0	0	0	0	0	0	0	
eyeFI	0	0	0	0	0	0	0	
eyeGR	0	0	0	0	0	0	0	
eyeHE	0	0	0	0	0	0	0	
eyeID	0	0	0	0	0	0	0	
eyeLE	0	0	0	0	0	0	0	
eyeSH	0	0	0	0	0	0	0	
eyeSP	0	0	0	0	0	0	0	
eyeSE	0	0	0	0	0	0	0	
eyeTY	0	0	0	0	0	0	0	
eyeTH	0	0	0	0	0	0	0	
eyeVA	0	0	0	0	0	0	0	
lipCA	0	0	0	0	0	0	0	
lipER	0	0	0	0	0	0	0	
lipEU	0	0	0	0	0	0	0	
lipFI	0	0	0	0	0	0	0	
lipGR	0	0	0	0	0	0	0	
lipHE	0	0	0	0	0	0	0	
lipID	0	0	0	0	0	0	0	
lipLE	0	0	0	0	0	0	0	
lipSH	0	0	0	0	0	0	0	
lipSP	0	0	0	0	0	0	0	
lipSE	0	0	0	0	0	0	0	
lipTY	0	0	0	0	0	0	0	
lipTH	0	0	0	0	0	0	0	
lipVA	0	0	0	0	0	0	0	

Widget Javascript not detected.  It may not be installed properly. Did you enable the widgetsnbextension? If not, then run "jupyter nbextension enable --py --sys-prefix widgetsnbextension"

In [16]:
colors = 'CA, ER, EU, FI, GR, HE, ID, LE, SH, SP, SE, TY, TH, VA'.split(', ')
print(', lip'.join(colors))


CA, lipER, lipEU, lipFI, lipGR, lipHE, lipID, lipLE, lipSH, lipSP, lipSE, lipTY, lipTH, lipVA

In [ ]: