def test_sortcastings(self): """ Tests that sort_castings works as expected """ TESTVALUES = ( [], ## Empty does nothing ["Standard 4 Pipe", "Standard 4 Spring" ], ## 2 Castings in proper order already (does nothing) ["Standard 4 Spring", "Standard 4 Pipe"], ## Same castings reversed (4P -> 4S) ["Standard 2 Spring", "Standard 4 Spring", "Standard 4 Pipe"], ## 3 Scrambled Castings (4P -> 2S -> 4S) ["Standard 6 Spring", "Standard 4 Spring"] ## Missing Pipe and backwards (4S -> 6S) ) TESTRESULTS = ([], ["Standard 4 Pipe", "Standard 4 Spring"], [ "Standard 4 Pipe", "Standard 4 Spring" ], ["Standard 4 Pipe", "Standard 2 Spring", "Standard 4 Spring"], ["Standard 4 Spring", "Standard 6 Spring"]) for (test, result) in zip(TESTVALUES, TESTRESULTS): with self.subTest(test=test, result=result): casting = classes.CastingSet() casting._castings = [ methods.convert_to_casting(c) for c in test ] resultcasting = classes.CastingSet() resultcasting._castings = [ methods.convert_to_casting(c) for c in result ] casting._sortcastings() self.assertEqual(casting, resultcasting)
def test_castings_equal(self): """ Tests that two identical CastingSets evaluate as equal """ for castings in [ ("Standard 4 Pipe", "Standard 2 Spring"), ## Single small spring ("Standard 4 Pipe", "Standard 2 Spring", "Standard 4 Spring"), ## Compound Spring ("Standard 6 Pipe", "Standard 6 Pipe"), ## Single Large Spring (Probably Unnecessary) ("Standard 6 Pipe", "Standard 4 Spring", "Standard 6 Pipe" ), ## Compound Large Springs (Also, probably unnecessary) ]: with self.subTest(castings=castings): c1 = classes.CastingSet(*castings) c2 = classes.CastingSet(*castings) self.assertEqual(c1, c2)
def getcasting(*springs): """ Attempts to select castings for the given springs """ if not springs: return None nsprings = len(springs) springods = sorted([spring.od for spring in springs]) castings = [ casting for casting in constants.CASTINGS if casting['type'] == "pipe" and casting['springs'] >= nsprings and all(od in casting['ods'] for od in springods) ] ## There's a possibility we don't have a matching casting, so return None if not castings: return None ## We only have just so many casting options at this point... only one is going to work pipecasting = castings[0] springcastings = [] for od in springods: castings = [ casting for casting in constants.CASTINGS if casting['type'] == "spring" and od in casting['ods'] ] ## As above, maybe we don't have a valid casting: return None if not castings: return None ## Pick the casting with the smallest loss of space ## As above,this maya s well be castings[0] springcastings.append( min(castings, key=lambda casting: casting['innerloss'])) return classes.CastingSet(pipecasting, *springcastings)
def test_castings_not_equal(self): """ Tests that two different CastingSets evaluate as inequal """ for c1, c2 in [ (("Standard 4 Pipe", "Standard 2 Spring"), ("Standard 6 Pipe", "Standard 6 Spring")), ## Completely Different (("Standard 4 Pipe", "Standard 2 Spring"), ("Standard 2 Spring", "Standard 4 Pipe")), ## Reverse Order (("Standard 4 Pipe", "Standard 2 Spring", "Standard 4 Spring"), ("Standard 4 Pipe", "Standard 4 Spring")), ## Missing Casting ]: with self.subTest(c1=c1, c2=c2): C1 = classes.CastingSet() C2 = classes.CastingSet() C1._castings = [methods.convert_to_casting(c) for c in c1] C2._castings = [methods.convert_to_casting(c) for c in c2] self.assertNotEqual(C1, C2)
def test_name_to_casting(self): """ Tests that the CastingSet object automatically converts names to casting dicts """ ## Using CASTINGLOOKUP to garauntee the values exist for name in list(classes.CASTINGLOOKUP): with self.subTest(name=name): try: casting = classes.CastingSet(name) except Exception as e: self.fail(e, e.message) self.assertEqual(classes.CASTINGLOOKUP[name], casting.castings[0])
def test_getcasting(self): """ Tests the getcasting method for expected values. The expected output of this method should be inspected if castings are changed or added. """ for result, wires in [ (["Standard 4 Pipe", "Standard 2 Spring"], [ (.1875, 2.75), ]), ]: with self.subTest(result=result, wires=wires): result = classes.CastingSet(*result) springs = [classes.Spring(wire, od=od) for (wire, od) in wires] ## socket automatically runs getcasting when casting is False socket = classes.Socket(*springs, castings=False) self.assertEqual(socket.castings, result)
def test_addcasting(self): """ Tests that addcasting works as expected """ cs = classes.CastingSet() self.assertEqual(cs.castings, []) ## Testing add string cs.addcasting("Standard 4 Pipe") self.assertEqual(cs.castings, [ methods.convert_to_casting("Standard 4 Pipe"), ]) ## Testing add "Instance" (technically dict right now, unless it changes in the future) spring = methods.convert_to_casting("Standard 4 Spring") cs.addcasting(spring) self.assertEqual( cs.castings, [methods.convert_to_casting("Standard 4 Pipe"), spring]) ## Testing that new Pipe castings replace old ones (highlander rule) pipe = methods.convert_to_casting("Standard 6 Pipe") cs.addcasting(pipe) self.assertEqual(cs.castings, [pipe, spring])
(([], []), []), ( ## 2.75 Inch Spring ## Test ([ c.Spring( wire=.1875, od=2.75, ), ], ["Standard 4 Pipe", "Standard 2 Spring"]), ## Result [ c.Socket(c.Spring( wire=.1875, od=2.75, ), castings=c.CastingSet("Standard 4 Pipe", "Standard 2 Spring")) ]), ( ## 3.75 Inch Spring ## Test ([ c.Spring( wire=.3125, od=3.75, ), ], ["Standard 4 Pipe", "Standard 4 Spring"]), ## Result [ c.Socket(c.Spring(wire=.3125, od=3.75), castings=c.CastingSet("Standard 4 Pipe", "Standard 4 Spring")) ]),
def build_sockets(springs, castings): """ Builds Sockets from a list of springs and a corresponding list of Castings. springs should be a list of classes.Spring instances. castings should be a list of dictionaries taken from constants.CASTINGS or the string ['name'] value taken from that list. Returns a list of castings. Castings without Springs will simply be discarded. At least one Pipe Casting is required if any Springs are passed. Every Spring requires a Spring Casting. If the Springs and either type of Castings are mismatched (either too many of one or not enough) a ValueError is raised. """ if not all(isinstance(spring, classes.Spring) for spring in springs): raise TypeError("Springs should be a list of Spring objects.") _c = [] for casting in castings: ## Check if castings are strings and try to convert them if necessary. casting = convert_to_casting(casting) _c.append(casting) castings = _c del _c ## Sort Castings into pipe/spring types pipecastings, springcastings = [], [] for casting in sorted(castings, key=lambda casting: max(casting['ods']), reverse=True): if casting['type'] == 'pipe': pipecastings.append(casting) else: springcastings.append(casting) ## If there are no Springs, we can return an empty list since all ## Castings are extra and therefore disregarded if not springs: return [] ## There should be atleast one Pipe Casting if len(pipecastings) < 1: raise ValueError("At least one Pipe Casting is required") ## Every spring should have its own Spring Casting if len(springs) != len(springcastings): raise ValueError("Insufficient Spring Castings") ## Sort Springs by OD, wire, and coiledlength (largest->smallest on all categories) springs = sorted(sorted(sorted(springs, key=lambda spring: spring.coiledlength, reverse=True), key=lambda spring: spring.wirediameter, reverse=True), key=lambda spring: spring.od, reverse=True) sockets = [] """ Dev Note: Algorithm ( While Pipe Casting and Springs: Check Socket: If no socket: pop Pipe casting ( for Springs Check Next Spring: Check pipe casting fit Check previous spring fit If Next Spring: ( for spring castings Check next Spring Casting fit ) ) If not Spring and Spring Casting: If Pipe Casting doesn't have a single successful spring: raise Error add Socket to output clear socket continue from beginning Pop Spring and Spring Casting Add Spring and Spring Casting to Socket If no slots open on Socket: add Socket to output clear socket ) """ socket = None while (socket or pipecastings) and springs: if not socket: pipecasting = pipecastings.pop(0) castingset = classes.CastingSet(pipecasting) socket = classes.Socket(castings=castingset) spring = None springcasting = None for pospring in springs: ## If pipe casting can't accept the spring, skip it if pospring.od not in pipecasting['ods'][len(socket.springs):]: continue ## If previous spring can't accept the spring, skip it if socket.springs and socket.springs[-1].id < pospring.od: continue ## Otherwise we can find a spring casting for it for pocast in springcastings: if pospring.od in pocast['ods']: springcasting = pocast break if springcasting: spring = pospring break if not spring or not springcasting: ## Socket does not have any springs at all if not socket.springs: raise ValueError("Mismatched Pipe Casting") ## Socket is not a compound: store and restart with no socket sockets.append(socket) socket = None continue springs.remove(spring) springcastings.remove(springcasting) socket.addspring(spring) castingset.addcasting(springcasting) ## Socket is full: store and restart with no socket if len(socket.springs) == socket.castings.maxsprings: sockets.append(socket) socket = None continue ## Mismatched Springs if springs: raise ValueError(f"Mismatched Springs: {springs}") ## Mismatched Spring Castings if springcastings: raise ValueError("Mismatched Spring Castings: {springcastings}") ## If there's an open socket, add it to output if socket: sockets.append(socket) return sockets