mcmas

py-mcmas: A wrapper for the MCMAS engine and the ISPL language


API Documentation for py-mcmas. You can also return to the documentation root

Important Models

Important functions

 1"""py-mcmas: A wrapper for the MCMAS engine and the ISPL language
 2
 3<hr style="width:100%;border-bottom:3px solid black;">
 4
 5API Documentation for py-mcmas.  You can also [return to the documentation root](../)
 6
 7### **Important Models**
 8
 9* [`Simulation`](#Simulation),
10* [`ISPL`](#ISPL),
11* [`Agent`](#Agent),
12* [`Environment`](#Environment)
13
14### **Important functions**
15
16* [`mcmas.engine`](#engine)
17"""
18
19from mcmas.logic import And, Eq, Equal, If, false, symbols, true  # noqa
20
21# from . import ai  # noqa
22from . import ctx  # noqa
23from . import fmtk  # noqa
24from . import logic  # noqa
25from . import parser  # noqa
26from .ai import Society  # noqa
27from .engine import engine  # noqa
28from .fmtk import Specification  # noqa
29from .ispl import ISPL, Actions, Agent, DefaultAgent, Environment  # noqa
30from .models import spec  # noqa
31from .sim import Simulation  # noqa
32
33__all__ = [
34    # Core
35    "ISPL",
36    "Agent",
37    "Environment",
38    "Actions",
39    "DefaultAgent",
40    # ABCs
41    "Specification",
42    "Simulation",
43    "Society",
44    "engine",
45    "symbols",
46    # logic
47    "If",
48    "And",
49    "Equal",
50    "Eq",
51    # modules
52    "fmtk",
53    "ispl",
54    "spec",
55    "logic",
56    "parser",
57    "ctx",
58]  # noqa
class ISPL(mcmas.ispl.Fragment):
489class ISPL(Fragment):
490    """
491    Python wrapper for ISPL specifications.
492
493    This permits partials or "fragments", i.e. the specification need not be complete and ready to run.
494
495    See the ISPL reference here: http://mattvonrocketstein.github.io/py-mcmas/isplref
496    """
497
498    PROMPT_HINTS: typing.ClassVar = "Individual proper nouns refer to separate agents."
499
500    def __str__(self):
501        return f"<ISPL: agents={len(self.agents)} vars={len(self.environment.vars)}>"
502
503    def analyze_types(self):
504        types = super().analyze_types()
505        for agent in self.agents.values():
506            types += agent.analyze_types()
507        return list(set(types))
508
509    @classmethod
510    def get_trivial(kls, *args, **kwargs):
511        """
512        ISPL(...) notation.  Unlike ISPL(..)
513
514        This returns the trivial specification, with just enough
515        structure to validate and run, plus any optional
516        overrides.
517        """
518        kwargs.update(check_slice(*args))
519        title = kwargs.pop("title", "Minimal valid ISPL specification")
520        agents = kwargs.pop("agents", {"DefaultAgent": DefaultAgent})
521        environment = kwargs.pop("environment", Environment(...))
522        evaluation = kwargs.pop("evaluation", ["ticking if Environment.ticking=true"])
523        init_states = kwargs.pop("init_states", ["Environment.ticking=true"])
524        formulae = kwargs.pop("formulae", ["ticking"])
525        return kls(
526            title=title,
527            environment=environment,
528            evaluation=evaluation,
529            init_states=init_states,
530            formulae=formulae,
531            agents=agents,
532            **kwargs,
533        )
534
535    # Metadata: typing.ClassVar = fmtk.SpecificationMetadata
536    COMPARATOR: typing.ClassVar = abs
537    REQUIRED: typing.ClassVar = ["agents", "evaluation", "formulae"]
538    parser: typing.ClassVar
539    title: str = Field(
540        default="Untitled Model",
541        description="Optional title.  Used as a comment at the top of the file",
542    )
543    agents: typing.Dict[str, Agent] = Field(
544        default={},
545        description="All agents involved in this specification.  A map of {name: agent_object}",
546    )
547    environment: Environment = Field(
548        default={},
549        description="The environment for this specification",
550    )
551    fairness: typing.Dict[str, typing.List[str]] = Field(
552        default={},
553        description=(
554            "Specifies conditions that must hold infinitely often along "
555            "all execution paths, used to rule out unrealistic behaviors."
556        ),
557    )
558    init_states: typing.InitStateType = Field(
559        default=[],
560        description="Initial global states of the system when verification begins.",
561    )
562    evaluation: typing.EvalType = Field(
563        default=[],
564        description="Calculations, composites, aggregates that can be referenced in formulae",
565    )
566    groups: typing.GroupsType = Field(
567        default={},
568        description=(
569            "A map of {group_name: [member1, .. ]}"
570            "Defines collections of agents for use in group-based verification formulae."
571        ),
572    )
573
574    formulae: typing.FormulaeType = Field(
575        description=(
576            "A list of formulas.\n\n"
577            "These will be partitioned into true/false categories, per the rest of the model"
578        ),
579        default=[],
580    )
581    simulation: SimType = Field(
582        default=None,
583        description=(
584            "The result of simulating this specification.  "
585            "Empty if simulation has never been run"
586        ),
587    )
588    source_code: typing.Union[str, None] = Field(
589        default=None,
590        description=(
591            "Source code for this specification.  "
592            "Only available if the specification was loaded from raw ISPL"
593        ),
594    )
595
596    def __contains__(self, other):
597        """
598        Specification algebra.
599        """
600        if isinstance(other, Agent):
601            return other in self.agents.values()
602        else:
603            raise TypeError(f"__contains__ undefined for {[type(self), type(other)]}")
604
605    def __abs__(self) -> float:
606        """
607        Specification algebra.
608
609        Used in __lt__ and __gt__. For ISPL specifications this
610        is the cumulative sum of formulae complexity
611        """
612        return sum([c.score for c in self.analyze_complexity()])
613
614    def __iadd__(self, other):
615        """
616        Specification algebra.
617
618        This is for in-place addition, i.e. `spec+=Agent(..)`
619        """
620        upd = self + other
621        self.update(upd.model_dump())
622        return self
623
624    def __sub__(self, other):
625        """
626        Specification algebra.
627
628        ISPL - agent => removes agent from this agent list, if present.
629        """
630        # if isinstance(other, (ISPL,)):
631        #     return self.model_copy(update=other.model_dump())
632        # if isinstance(other, (Environment,)):
633        #     return self.model_copy(update=dict(environment=other))
634        if isinstance(other, (Agent,)):
635            agents = {}
636            for a in self.agents.values():
637                if a.name != other.name:
638                    agents[a.name] = a
639            return self.model_copy(update=dict(agents=agents))
640        raise TypeError(f"Cannot add {type(self)} and {type(other)}")
641
642    def __add__(self, other):
643        """
644        Specification algebra.
645
646        ISPL + agent => adds agent to spec ISPL + ISPL =>
647        overrides first spec with values from 2nd
648        """
649        if isinstance(other, (ISPL,)):
650            return self.model_copy(update=other.model_dump())
651        if isinstance(other, (Environment,)):
652            return self.model_copy(update=dict(environment=other))
653        if isinstance(other, (Agent,)):
654            agents = self.agents
655            agents.update(**{other.name: other})
656            return self.model_copy(update=dict(agents=agents))
657        raise TypeError(f"Cannot add {type(self)} and {type(other)}")
658
659    def model_dump_source(self):
660        """
661        Dump the source-code for this piece of the specification.
662        """
663        # from mcmas.engine import dict2ispl
664        return util.dict2ispl(self.model_dump())
665
666    @util.classproperty
667    def parser(self):
668        """
669        Shortcut for `mcmas.parser.parse`
670        """
671        from mcmas import parser
672
673        return parser.parse
674
675    @property
676    def local_advice(self) -> list:
677        """
678        
679        """
680        out = []
681        for agent in self.agents:
682            agentish = self.agents[agent]
683            out += agentish.advice
684        return out
685
686    @classmethod
687    @pydantic.validate_call
688    def load_from_source(
689        kls, txt, strict: bool = False
690    ) -> typing.Dict[str, typing.Self]:
691        """
692        Return ISPL object from given string.
693        """
694        # from mcmas import parser
695        return kls.parser(txt)
696
697    @classmethod
698    @pydantic.validate_call
699    def load_from_ispl_file(
700        kls,
701        file: Optional[str] = None,
702    ):
703        """
704        Return ISPL object from contents of given file.
705        """
706        LOGGER.debug(f"ISPL.load_from_ispl_file: {file}")
707        metadata = dict(file=file)
708        if file:
709            assert os.path.exists(file), f"no such file: {file}"
710            if file.endswith("ispl"):
711                with open(file) as fhandle:
712                    text = fhandle.read()
713                    tmp = kls.parser(text, file=file)
714                    data = tmp.model_dump(exclude="metadata")
715                    return ISPL(metadata=ISPL.Metadata(**metadata), **data)
716            #    return ISPL(metadata=dict(file=file, engine="bonk"), **data)
717            elif file.endswith("json"):
718                raise ValueError("refusing to work with ispl file")
719            elif file in ["-", "/dev/stdin"]:
720                metadata.update(file="<<stream>>")
721                text = sys.stdin.read().strip()
722                data = kls.parser(text).model_dump(exclude="metadata")
723                return ISPL(metadata=ISPL.Metadata(**metadata), **data)
724            else:
725                LOGGER.critical(f"could not create model from {file}")
726                raise Exception(file)
727        if text:
728            LOGGER.critical("NIY")
729            raise Exception(text)
730
731    load_from_file = load_from_ispl_file
732
733    @classmethod
734    @pydantic.validate_call
735    def load_from_json_file(kls, file: Optional[str] = None, text=None):
736        """
737        Return ISPL object from the contents of given file.
738
739        File *must* be JSON encoded.
740        """
741        LOGGER.critical(f"ISPL.load_from_json_file: {file}")
742        metadata = dict(file=file)
743        if file:
744            assert os.path.exists(file), f"no such file: {file}"
745            if file.endswith("json"):
746                with open(file) as fhandle:
747                    data = json.loads(fhandle.read())
748                return ISPL(metadata=ISPL.Metadata(**metadata), **data)
749            elif file.endswith("ispl"):
750                raise ValueError("refusing to work with ispl file")
751            elif file in ["-", "/dev/stdin"]:
752                metadata.update(file="<<stream>>")
753                text = sys.stdin.read().strip()
754                data = json.loads(text)
755                return ISPL(
756                    metadata=ISPL.Metadata(**{**data.pop("metadata", {}), **metadata}),
757                    **data,
758                )
759            else:
760                LOGGER.critical(f"could not create model from {file}")
761                raise Exception(file)
762
763        if text:
764            LOGGER.critical("NIY")
765            raise Exception(text)
766
767    @property
768    @pydantic.validate_call
769    def validates(self) -> bool:
770        """
771        Asks the engine directly whether this spec validates.
772
773        Note that this is ground-truth and not heuristic like the `valid` property elsewhere!
774        """
775        return mcmas.engine.validate(model=self)
776
777    @property
778    @pydantic.validate_call
779    def analysis(self) -> spec.Analysis:
780        """
781        Static-analysis for this ISPL specification.
782
783        Returns details about symbols and logical operators.
784        """
785        ents = list(self.agents.values())
786        ents += [self.environment]
787        vars = []
788        for agent in ents:
789            if isinstance(agent, (Agent,)):
790                vars += [k.strip() for k in (agent.lobsvars or []) if k.strip()]
791            vars += [k.strip() for k in (agent.vars or []) if k.strip()]
792        vars = list(set(vars))
793        meta = dict(
794            symbols=spec.SymbolMetadata(
795                agents=[agent for agent in self.agents],
796                vars=vars,
797                actions=list(
798                    set(
799                        functools.reduce(
800                            operator.add,
801                            [self.agents[agent].actions for agent in self.agents],
802                        )
803                    )
804                ),
805            ),
806            types=self.analyze_types(),
807            complexity=self.analyze_complexity(),
808        )
809        meta = spec.Analysis(**meta)
810        return meta
811
812    def analyze_complexity(self) -> typing.List:
813        from mcmas.logic import complexity
814
815        return [
816            complexity.analyzer.analyze(f.lstrip().rstrip(), index=i)
817            for i, f in enumerate(self.formulae)
818        ]
819
820    def exec(self, strict: bool = False, **kwargs) -> typing.Self:
821        """
822        Execute this ISPL specification.
823        """
824        required = ["init_states"]
825        self.logger.debug(f"validating: {self.source_code or self.model_dump_source()}")
826
827        # check advice before execution
828        if self.advice:
829            msg = "exec: Model has non-empty advice!"
830            LOGGER.critical(msg)
831            if strict:
832                raise RuntimeError(msg)
833
834        for k in required:
835            if not getattr(self, k):
836                err = f"Validation failed.  Required key `{k}` is missing."
837                return self.model_copy(
838                    update={
839                        # "source_code": src,
840                        "simulation": Simulation(
841                            error=err,
842                            metadata=Simulation.Metadata(parsed=False, validates=False),
843                        ),
844                    }
845                )
846
847        self.logger.debug("starting..")
848        sim = mcmas.engine(text=self.model_dump_source(), output_format="model")
849        out = self.model_copy(
850            update=dict(
851                source_code=self.source_code or self.model_dump_source(),
852                simulation=sim,
853                metadata=self.metadata.model_dump(),
854            )
855        )
856        self.logger.debug("done")
857        return out
858
859    run_sim = run_simulation = exec
860
861    def repl(self):
862        """
863        Start a REPL shell with this object available as `spec`.
864        """
865        result_model = self.exec()
866        return util.repl(spec=result_model)

Python wrapper for ISPL specifications.

This permits partials or "fragments", i.e. the specification need not be complete and ready to run.

See the ISPL reference here: http://mattvonrocketstein.github.io/py-mcmas/isplref

PROMPT_HINTS: ClassVar = 'Individual proper nouns refer to separate agents.'
def analyze_types(self):
503    def analyze_types(self):
504        types = super().analyze_types()
505        for agent in self.agents.values():
506            types += agent.analyze_types()
507        return list(set(types))
@classmethod
def get_trivial(kls, *args, **kwargs):
509    @classmethod
510    def get_trivial(kls, *args, **kwargs):
511        """
512        ISPL(...) notation.  Unlike ISPL(..)
513
514        This returns the trivial specification, with just enough
515        structure to validate and run, plus any optional
516        overrides.
517        """
518        kwargs.update(check_slice(*args))
519        title = kwargs.pop("title", "Minimal valid ISPL specification")
520        agents = kwargs.pop("agents", {"DefaultAgent": DefaultAgent})
521        environment = kwargs.pop("environment", Environment(...))
522        evaluation = kwargs.pop("evaluation", ["ticking if Environment.ticking=true"])
523        init_states = kwargs.pop("init_states", ["Environment.ticking=true"])
524        formulae = kwargs.pop("formulae", ["ticking"])
525        return kls(
526            title=title,
527            environment=environment,
528            evaluation=evaluation,
529            init_states=init_states,
530            formulae=formulae,
531            agents=agents,
532            **kwargs,
533        )

ISPL(...) notation. Unlike ISPL(..)

This returns the trivial specification, with just enough structure to validate and run, plus any optional overrides.

def COMPARATOR(x, /):

Return the absolute value of the argument.

REQUIRED: ClassVar = ['agents', 'evaluation', 'formulae']
def parser(unknown):

Shortcut for mcmas.parser.parse

title: str
agents: Dict[str, Agent]
environment: Environment
fairness: Dict[str, List[str]]
init_states: Annotated[List[Annotated[Union[str, mcmas.logic.Symbol], BeforeValidator(func=<class 'str'>, json_schema_input_type=PydanticUndefined)]], BeforeValidator(func=<function ensure_list at 0x7fd1247758a0>, json_schema_input_type=PydanticUndefined)]
evaluation: Annotated[List[Annotated[Union[str, mcmas.logic.Symbol], BeforeValidator(func=<class 'str'>, json_schema_input_type=PydanticUndefined)]], BeforeValidator(func=<function ensure_list at 0x7fd1247758a0>, json_schema_input_type=PydanticUndefined)]
groups: Annotated[Optional[Dict[str, List[Annotated[Union[str, mcmas.logic.Symbol], BeforeValidator(func=<class 'str'>, json_schema_input_type=PydanticUndefined)]]]], BeforeValidator(func=<function ensure_groups at 0x7fd1247759e0>, json_schema_input_type=PydanticUndefined)]
formulae: Annotated[List[Annotated[Union[str, mcmas.logic.Symbol], BeforeValidator(func=<class 'str'>, json_schema_input_type=PydanticUndefined)]], BeforeValidator(func=<function ensure_list at 0x7fd1247758a0>, json_schema_input_type=PydanticUndefined)]
simulation: Optional[Simulation]
source_code: Optional[str]
def model_dump_source(self):
659    def model_dump_source(self):
660        """
661        Dump the source-code for this piece of the specification.
662        """
663        # from mcmas.engine import dict2ispl
664        return util.dict2ispl(self.model_dump())

Dump the source-code for this piece of the specification.

local_advice: list
675    @property
676    def local_advice(self) -> list:
677        """
678        
679        """
680        out = []
681        for agent in self.agents:
682            agentish = self.agents[agent]
683            out += agentish.advice
684        return out
@classmethod
@pydantic.validate_call
def load_from_source(kls, txt, strict: bool = False) -> Dict[str, Self]:
686    @classmethod
687    @pydantic.validate_call
688    def load_from_source(
689        kls, txt, strict: bool = False
690    ) -> typing.Dict[str, typing.Self]:
691        """
692        Return ISPL object from given string.
693        """
694        # from mcmas import parser
695        return kls.parser(txt)

Return ISPL object from given string.

@classmethod
@pydantic.validate_call
def load_from_ispl_file(kls, file: Optional[str] = None):
697    @classmethod
698    @pydantic.validate_call
699    def load_from_ispl_file(
700        kls,
701        file: Optional[str] = None,
702    ):
703        """
704        Return ISPL object from contents of given file.
705        """
706        LOGGER.debug(f"ISPL.load_from_ispl_file: {file}")
707        metadata = dict(file=file)
708        if file:
709            assert os.path.exists(file), f"no such file: {file}"
710            if file.endswith("ispl"):
711                with open(file) as fhandle:
712                    text = fhandle.read()
713                    tmp = kls.parser(text, file=file)
714                    data = tmp.model_dump(exclude="metadata")
715                    return ISPL(metadata=ISPL.Metadata(**metadata), **data)
716            #    return ISPL(metadata=dict(file=file, engine="bonk"), **data)
717            elif file.endswith("json"):
718                raise ValueError("refusing to work with ispl file")
719            elif file in ["-", "/dev/stdin"]:
720                metadata.update(file="<<stream>>")
721                text = sys.stdin.read().strip()
722                data = kls.parser(text).model_dump(exclude="metadata")
723                return ISPL(metadata=ISPL.Metadata(**metadata), **data)
724            else:
725                LOGGER.critical(f"could not create model from {file}")
726                raise Exception(file)
727        if text:
728            LOGGER.critical("NIY")
729            raise Exception(text)

Return ISPL object from contents of given file.

@classmethod
@pydantic.validate_call
def load_from_file(kls, file: Optional[str] = None):
697    @classmethod
698    @pydantic.validate_call
699    def load_from_ispl_file(
700        kls,
701        file: Optional[str] = None,
702    ):
703        """
704        Return ISPL object from contents of given file.
705        """
706        LOGGER.debug(f"ISPL.load_from_ispl_file: {file}")
707        metadata = dict(file=file)
708        if file:
709            assert os.path.exists(file), f"no such file: {file}"
710            if file.endswith("ispl"):
711                with open(file) as fhandle:
712                    text = fhandle.read()
713                    tmp = kls.parser(text, file=file)
714                    data = tmp.model_dump(exclude="metadata")
715                    return ISPL(metadata=ISPL.Metadata(**metadata), **data)
716            #    return ISPL(metadata=dict(file=file, engine="bonk"), **data)
717            elif file.endswith("json"):
718                raise ValueError("refusing to work with ispl file")
719            elif file in ["-", "/dev/stdin"]:
720                metadata.update(file="<<stream>>")
721                text = sys.stdin.read().strip()
722                data = kls.parser(text).model_dump(exclude="metadata")
723                return ISPL(metadata=ISPL.Metadata(**metadata), **data)
724            else:
725                LOGGER.critical(f"could not create model from {file}")
726                raise Exception(file)
727        if text:
728            LOGGER.critical("NIY")
729            raise Exception(text)

Return ISPL object from contents of given file.

@classmethod
@pydantic.validate_call
def load_from_json_file(kls, file: Optional[str] = None, text=None):
733    @classmethod
734    @pydantic.validate_call
735    def load_from_json_file(kls, file: Optional[str] = None, text=None):
736        """
737        Return ISPL object from the contents of given file.
738
739        File *must* be JSON encoded.
740        """
741        LOGGER.critical(f"ISPL.load_from_json_file: {file}")
742        metadata = dict(file=file)
743        if file:
744            assert os.path.exists(file), f"no such file: {file}"
745            if file.endswith("json"):
746                with open(file) as fhandle:
747                    data = json.loads(fhandle.read())
748                return ISPL(metadata=ISPL.Metadata(**metadata), **data)
749            elif file.endswith("ispl"):
750                raise ValueError("refusing to work with ispl file")
751            elif file in ["-", "/dev/stdin"]:
752                metadata.update(file="<<stream>>")
753                text = sys.stdin.read().strip()
754                data = json.loads(text)
755                return ISPL(
756                    metadata=ISPL.Metadata(**{**data.pop("metadata", {}), **metadata}),
757                    **data,
758                )
759            else:
760                LOGGER.critical(f"could not create model from {file}")
761                raise Exception(file)
762
763        if text:
764            LOGGER.critical("NIY")
765            raise Exception(text)

Return ISPL object from the contents of given file.

File must be JSON encoded.

validates: bool
767    @property
768    @pydantic.validate_call
769    def validates(self) -> bool:
770        """
771        Asks the engine directly whether this spec validates.
772
773        Note that this is ground-truth and not heuristic like the `valid` property elsewhere!
774        """
775        return mcmas.engine.validate(model=self)

Asks the engine directly whether this spec validates.

Note that this is ground-truth and not heuristic like the valid property elsewhere!

analysis: mcmas.models.spec.Analysis
777    @property
778    @pydantic.validate_call
779    def analysis(self) -> spec.Analysis:
780        """
781        Static-analysis for this ISPL specification.
782
783        Returns details about symbols and logical operators.
784        """
785        ents = list(self.agents.values())
786        ents += [self.environment]
787        vars = []
788        for agent in ents:
789            if isinstance(agent, (Agent,)):
790                vars += [k.strip() for k in (agent.lobsvars or []) if k.strip()]
791            vars += [k.strip() for k in (agent.vars or []) if k.strip()]
792        vars = list(set(vars))
793        meta = dict(
794            symbols=spec.SymbolMetadata(
795                agents=[agent for agent in self.agents],
796                vars=vars,
797                actions=list(
798                    set(
799                        functools.reduce(
800                            operator.add,
801                            [self.agents[agent].actions for agent in self.agents],
802                        )
803                    )
804                ),
805            ),
806            types=self.analyze_types(),
807            complexity=self.analyze_complexity(),
808        )
809        meta = spec.Analysis(**meta)
810        return meta

Static-analysis for this ISPL specification.

Returns details about symbols and logical operators.

def analyze_complexity(self) -> List:
812    def analyze_complexity(self) -> typing.List:
813        from mcmas.logic import complexity
814
815        return [
816            complexity.analyzer.analyze(f.lstrip().rstrip(), index=i)
817            for i, f in enumerate(self.formulae)
818        ]
def exec(self, strict: bool = False, **kwargs) -> Self:
820    def exec(self, strict: bool = False, **kwargs) -> typing.Self:
821        """
822        Execute this ISPL specification.
823        """
824        required = ["init_states"]
825        self.logger.debug(f"validating: {self.source_code or self.model_dump_source()}")
826
827        # check advice before execution
828        if self.advice:
829            msg = "exec: Model has non-empty advice!"
830            LOGGER.critical(msg)
831            if strict:
832                raise RuntimeError(msg)
833
834        for k in required:
835            if not getattr(self, k):
836                err = f"Validation failed.  Required key `{k}` is missing."
837                return self.model_copy(
838                    update={
839                        # "source_code": src,
840                        "simulation": Simulation(
841                            error=err,
842                            metadata=Simulation.Metadata(parsed=False, validates=False),
843                        ),
844                    }
845                )
846
847        self.logger.debug("starting..")
848        sim = mcmas.engine(text=self.model_dump_source(), output_format="model")
849        out = self.model_copy(
850            update=dict(
851                source_code=self.source_code or self.model_dump_source(),
852                simulation=sim,
853                metadata=self.metadata.model_dump(),
854            )
855        )
856        self.logger.debug("done")
857        return out

Execute this ISPL specification.

def repl(self):
861    def repl(self):
862        """
863        Start a REPL shell with this object available as `spec`.
864        """
865        result_model = self.exec()
866        return util.repl(spec=result_model)

Start a REPL shell with this object available as spec.

model_config: ClassVar[pydantic.config.ConfigDict] = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

def run_sim(self, strict: bool = False, **kwargs) -> Self:
820    def exec(self, strict: bool = False, **kwargs) -> typing.Self:
821        """
822        Execute this ISPL specification.
823        """
824        required = ["init_states"]
825        self.logger.debug(f"validating: {self.source_code or self.model_dump_source()}")
826
827        # check advice before execution
828        if self.advice:
829            msg = "exec: Model has non-empty advice!"
830            LOGGER.critical(msg)
831            if strict:
832                raise RuntimeError(msg)
833
834        for k in required:
835            if not getattr(self, k):
836                err = f"Validation failed.  Required key `{k}` is missing."
837                return self.model_copy(
838                    update={
839                        # "source_code": src,
840                        "simulation": Simulation(
841                            error=err,
842                            metadata=Simulation.Metadata(parsed=False, validates=False),
843                        ),
844                    }
845                )
846
847        self.logger.debug("starting..")
848        sim = mcmas.engine(text=self.model_dump_source(), output_format="model")
849        out = self.model_copy(
850            update=dict(
851                source_code=self.source_code or self.model_dump_source(),
852                simulation=sim,
853                metadata=self.metadata.model_dump(),
854            )
855        )
856        self.logger.debug("done")
857        return out

Execute this ISPL specification.

def run_simulation(self, strict: bool = False, **kwargs) -> Self:
820    def exec(self, strict: bool = False, **kwargs) -> typing.Self:
821        """
822        Execute this ISPL specification.
823        """
824        required = ["init_states"]
825        self.logger.debug(f"validating: {self.source_code or self.model_dump_source()}")
826
827        # check advice before execution
828        if self.advice:
829            msg = "exec: Model has non-empty advice!"
830            LOGGER.critical(msg)
831            if strict:
832                raise RuntimeError(msg)
833
834        for k in required:
835            if not getattr(self, k):
836                err = f"Validation failed.  Required key `{k}` is missing."
837                return self.model_copy(
838                    update={
839                        # "source_code": src,
840                        "simulation": Simulation(
841                            error=err,
842                            metadata=Simulation.Metadata(parsed=False, validates=False),
843                        ),
844                    }
845                )
846
847        self.logger.debug("starting..")
848        sim = mcmas.engine(text=self.model_dump_source(), output_format="model")
849        out = self.model_copy(
850            update=dict(
851                source_code=self.source_code or self.model_dump_source(),
852                simulation=sim,
853                metadata=self.metadata.model_dump(),
854            )
855        )
856        self.logger.debug("done")
857        return out

Execute this ISPL specification.

class Agent(mcmas.ispl.Fragment):
288class Agent(Fragment):
289    """
290    Python wrapper for ISPL Agents.
291
292    See also the relevant [ISPL reference](http://mattvonrocketstein.github.io/py-mcmas/isplref/#agent)
293    """
294
295    COMPARATOR: typing.ClassVar = len
296    DefaultAgent: typing.ClassVar
297    REQUIRED: typing.ClassVar = ["protocol", "evolution", "actions"]
298    parser: typing.ClassVar
299
300    name: str = Field(
301        default="player",
302        description=("Name of this agent"),
303    )
304    actions: typing.ActionsType = ActionsField
305    evolution: typing.EvolType = EvolutionField
306    obsvars: typing.ObsVarsType = ObsvarsField
307    protocol: typing.ProtocolType = ProtocolField
308    vars: typing.VarsType = VarsField
309
310    lobsvars: typing.LobsvarsType = Field(
311        description="",
312        default=[],
313    )
314    red_states: typing.List[str] = Field(
315        description="",
316        default=[],
317    )
318
319    __len__ = Environment.__len__
320
321    @util.classproperty
322    def DefaultAgent(kls):
323        """
324        Returns the trivial agent.
325        """
326        return kls(...)
327
328    @classmethod
329    def _load_from_pydantic_ai_agent(kls, pagent, **extra) -> typing.Self:
330        """
331        Create ISPL agent from the given pydantic agent.
332        """
333        kls.logger.info(f"_load_from_pydantic_ai_agent: {pagent}")
334        from mcmas import util
335
336        name = pagent.name or f"Agent_{id(pagent)}"
337        actions = list(pagent._function_toolset.tools.keys())
338        tools = list(pagent._function_toolset.tools.values())
339        tool = tools[0]
340        if len(tools) > 1:
341            kls.logger.warning(f"pydantic agent `{name}` has multiple tools!")
342            kls.logger.warning(f"using just the first one: {tool}")
343        # naive conversion from function signature to ISPL types.
344        vars = util.fxn_sig.as_ispl_types(tool.function)
345        vars.pop("ctx", None)
346        return Agent(
347            name=name,
348            actions=actions,
349            vars=vars,
350            metadata=dict(
351                parser=f"{kls.__module__}.{kls.__name__}._load_from_pydantic_ai_agent",
352                file=inspect.getfile(pagent.__class__),
353            ),
354            **extra,
355        ).model_completion()
356
357    def analyze_symbols(self) -> typing.List[str]:
358        """
359        Returns symbol-related metadata including agent-names,
360        actions, variables, and type info.
361        """
362        return spec.SymbolMetadata(
363            agents=[self.name],
364            actions=self.actions,
365            vars=[k.strip() for k in list(self.lobsvars) + list(self.vars)],
366            types=self.analyze_types(),
367        )
368
369    @property
370    @pydantic.validate_call
371    def analysis(self) -> spec.Analysis:
372        """
373        Static-analysis for this Agent specification.
374
375        Returns details about symbols and logical operators.
376        """
377        return spec.Analysis(
378            symbols=self.analyze_symbols(),
379            types=self.analyze_types(),
380            complexity=self.analyze_complexity(),
381        )
382        # out.actions = [getattr(symbols, x) for x in sorted(list(set(out.actions)))]
383        # out.vars = [getattr(symbols, x) for x in sorted(list(set(out.vars)))]
384        # out.agents = [getattr(symbols, x) for x in sorted(list(set(out.agents)))]
385        # return meta
386
387    def __pow__(self, other: float = 0.1) -> typing.Self:
388        """
389        
390        """
391        from mcmas import ai
392
393        return ai.model_mutation(obj=self, model_settings=dict(top_p=other))
394
395    def __invert__(self) -> typing.Self:
396        """
397        Trigger completion for this Agent.
398
399        This is NOT backed by an LLM; see instead `mcmas.ai.agent_completion`.
400        """
401        if not self.advice:
402            err = f"{self} is already valid, returning it instead of completing"
403            LOGGER.warning(err)
404            return self
405        else:
406            tmp = self
407            trivial = DefaultAgent
408            defaults = dict(
409                protocol=trivial.protocol,
410                vars=trivial.vars,
411                evolution=trivial.evolution,
412            )
413            for x in ["protocol", "vars", "evolution"]:
414                if getattr(self, x, None):
415                    pass
416                else:
417                    LOGGER.warning(f"could not find required {x}")
418                    tmp = tmp.model_copy(update={x: defaults[x]})
419            return tmp.model_copy(
420                update=dict(actions=list(set(tmp.actions + trivial.actions)))
421            )
422
423    model_completion = __invert__
424
425    @classmethod
426    def get_trivial(kls, *args, **kwargs):
427        """
428        Smallest legal agent.
429        """
430        kwargs.update(check_slice(*args))
431        return kls(
432            name="trivial",
433            vars=dict(ticking="boolean"),
434            actions=["tick"],
435            protocol=["Other : {tick}"],
436            evolution=["ticking=true if Action=tick;"],
437        )
438
439    def model_dump_source(self):
440        """
441        Dump the source-code for this piece of the specification.
442        """
443        from mcmas import rendering
444
445        return rendering.get_template("Agent.j2").render(
446            name=self.name, agent=self.model_dump()
447        )
448
449    @util.classproperty
450    def parser(self) -> typing.Callable:
451        """
452        Return an appropriate parser for this spec-fragment.
453        """
454        from mcmas import parser
455
456        return parser.extract_agents
457
458    @classmethod
459    @pydantic.validate_call
460    def load_from_source(kls, txt, strict: bool = False) -> typing.Self:
461        """
462        Load ISPL agent from string.
463        """
464        agents = kls.parser(txt)
465        agents.pop("Environment", None)
466        if len(agents) != 1:
467            LOGGER.critical("load_from_source: more than 1 agent! returning first..")
468        return Agent(**list(agents.values())[0])
469
470    @property
471    def local_advice(self) -> list:
472        return []

Python wrapper for ISPL Agents.

See also the relevant ISPL reference

def COMPARATOR(obj, /):

Return the number of items in a container.

def DefaultAgent(unknown):

Returns the trivial agent.

REQUIRED: ClassVar = ['protocol', 'evolution', 'actions']
def parser(unknown):

Return an appropriate parser for this spec-fragment.

name: str
actions: Annotated[List[Annotated[Union[str, mcmas.logic.Symbol], BeforeValidator(func=<class 'str'>, json_schema_input_type=PydanticUndefined)]], BeforeValidator(func=<function ensure_list at 0x7fd1247758a0>, json_schema_input_type=PydanticUndefined)]
evolution: Annotated[List[Annotated[Union[str, mcmas.logic.Symbol], BeforeValidator(func=<class 'str'>, json_schema_input_type=PydanticUndefined)]], BeforeValidator(func=<function ensure_list at 0x7fd1247758a0>, json_schema_input_type=PydanticUndefined)]
obsvars: Annotated[Dict[Annotated[Union[str, mcmas.logic.Symbol], BeforeValidator(func=<class 'str'>, json_schema_input_type=PydanticUndefined)], Annotated[Union[str, mcmas.logic.Symbol], BeforeValidator(func=<class 'str'>, json_schema_input_type=PydanticUndefined)]], BeforeValidator(func=<function ensure_dict at 0x7fd124775b20>, json_schema_input_type=PydanticUndefined)]
protocol: Annotated[Dict[Annotated[Union[str, mcmas.logic.Symbol], BeforeValidator(func=<class 'str'>, json_schema_input_type=PydanticUndefined)], Annotated[Union[str, mcmas.logic.Symbol], BeforeValidator(func=<class 'str'>, json_schema_input_type=PydanticUndefined)]], BeforeValidator(func=<function ensure_protocol at 0x7fd124775940>, json_schema_input_type=PydanticUndefined)]
vars: Annotated[Optional[Dict[Annotated[Union[str, mcmas.logic.Symbol], BeforeValidator(func=<class 'str'>, json_schema_input_type=PydanticUndefined)], Annotated[Union[str, mcmas.logic.Symbol], BeforeValidator(func=<class 'str'>, json_schema_input_type=PydanticUndefined)]]], BeforeValidator(func=<function ensure_dict at 0x7fd124775b20>, json_schema_input_type=PydanticUndefined)]
lobsvars: Annotated[List[Annotated[Union[str, mcmas.logic.Symbol], BeforeValidator(func=<class 'str'>, json_schema_input_type=PydanticUndefined)]], BeforeValidator(func=<function ensure_list at 0x7fd1247758a0>, json_schema_input_type=PydanticUndefined)]
red_states: List[str]
def analyze_symbols(self) -> List[str]:
357    def analyze_symbols(self) -> typing.List[str]:
358        """
359        Returns symbol-related metadata including agent-names,
360        actions, variables, and type info.
361        """
362        return spec.SymbolMetadata(
363            agents=[self.name],
364            actions=self.actions,
365            vars=[k.strip() for k in list(self.lobsvars) + list(self.vars)],
366            types=self.analyze_types(),
367        )

Returns symbol-related metadata including agent-names, actions, variables, and type info.

analysis: mcmas.models.spec.Analysis
369    @property
370    @pydantic.validate_call
371    def analysis(self) -> spec.Analysis:
372        """
373        Static-analysis for this Agent specification.
374
375        Returns details about symbols and logical operators.
376        """
377        return spec.Analysis(
378            symbols=self.analyze_symbols(),
379            types=self.analyze_types(),
380            complexity=self.analyze_complexity(),
381        )
382        # out.actions = [getattr(symbols, x) for x in sorted(list(set(out.actions)))]
383        # out.vars = [getattr(symbols, x) for x in sorted(list(set(out.vars)))]
384        # out.agents = [getattr(symbols, x) for x in sorted(list(set(out.agents)))]
385        # return meta

Static-analysis for this Agent specification.

Returns details about symbols and logical operators.

def model_completion(self) -> Self:
395    def __invert__(self) -> typing.Self:
396        """
397        Trigger completion for this Agent.
398
399        This is NOT backed by an LLM; see instead `mcmas.ai.agent_completion`.
400        """
401        if not self.advice:
402            err = f"{self} is already valid, returning it instead of completing"
403            LOGGER.warning(err)
404            return self
405        else:
406            tmp = self
407            trivial = DefaultAgent
408            defaults = dict(
409                protocol=trivial.protocol,
410                vars=trivial.vars,
411                evolution=trivial.evolution,
412            )
413            for x in ["protocol", "vars", "evolution"]:
414                if getattr(self, x, None):
415                    pass
416                else:
417                    LOGGER.warning(f"could not find required {x}")
418                    tmp = tmp.model_copy(update={x: defaults[x]})
419            return tmp.model_copy(
420                update=dict(actions=list(set(tmp.actions + trivial.actions)))
421            )

Trigger completion for this Agent.

This is NOT backed by an LLM; see instead mcmas.ai.agent_completion.

@classmethod
def get_trivial(kls, *args, **kwargs):
425    @classmethod
426    def get_trivial(kls, *args, **kwargs):
427        """
428        Smallest legal agent.
429        """
430        kwargs.update(check_slice(*args))
431        return kls(
432            name="trivial",
433            vars=dict(ticking="boolean"),
434            actions=["tick"],
435            protocol=["Other : {tick}"],
436            evolution=["ticking=true if Action=tick;"],
437        )

Smallest legal agent.

def model_dump_source(self):
439    def model_dump_source(self):
440        """
441        Dump the source-code for this piece of the specification.
442        """
443        from mcmas import rendering
444
445        return rendering.get_template("Agent.j2").render(
446            name=self.name, agent=self.model_dump()
447        )

Dump the source-code for this piece of the specification.

@classmethod
@pydantic.validate_call
def load_from_source(kls, txt, strict: bool = False) -> Self:
458    @classmethod
459    @pydantic.validate_call
460    def load_from_source(kls, txt, strict: bool = False) -> typing.Self:
461        """
462        Load ISPL agent from string.
463        """
464        agents = kls.parser(txt)
465        agents.pop("Environment", None)
466        if len(agents) != 1:
467            LOGGER.critical("load_from_source: more than 1 agent! returning first..")
468        return Agent(**list(agents.values())[0])

Load ISPL agent from string.

local_advice: list
470    @property
471    def local_advice(self) -> list:
472        return []

Subclassers must implement this.

model_config: ClassVar[pydantic.config.ConfigDict] = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class Environment(mcmas.ispl.Fragment):
223class Environment(Fragment):
224    """
225    Python wrapper for ISPL Environments. Environments model the
226    shared information and boundary conditions that all other
227    agents can observe.
228
229    See also the relevant [ISPL reference](http://mattvonrocketstein.github.io/py-mcmas/isplref/#environment)
230    """
231
232    REQUIRED: typing.ClassVar = ["protocol", "actions"]
233    DefaultEnvironment: typing.ClassVar
234    actions: typing.ActionsType = ActionsField
235    vars: typing.VarsType = VarsField
236    obsvars: typing.ObsVarsType = ObsvarsField
237    evolution: typing.EvolType = EvolutionField
238    protocol: typing.ProtocolType = ProtocolField
239
240    def __len__(self):
241        """
242        Specification Algebra.
243
244        The length of an agent is the number of items in the
245        description length
246        """
247        return sum(
248            map(
249                len,
250                [
251                    self.actions,
252                    self.evolution,
253                    self.obsvars,
254                    self.protocol,
255                    self.vars,
256                    getattr(self, "lobsvars", []),
257                    getattr(self, "red_states", []),
258                ],
259            )
260        )
261
262    @util.classproperty
263    def DefaultEnvironment(kls):
264        return kls(...)
265
266    @classmethod
267    def get_trivial(kls, *args, **kwargs):
268        kwargs.update(check_slice(*args))
269        actions = kwargs.pop("actions", [symbols.tick])
270        protocol = kwargs.pop("protocol", dict(Other=[symbols.tick]))
271        vars = kwargs.pop("vars", dict(ticking="boolean"))
272        return kls(vars=vars, actions=actions, protocol=protocol, **kwargs)
273
274    @classmethod
275    @pydantic.validate_call
276    def load_from_source(kls, txt: str) -> typing.Self:
277        """
278        Creates an Environment from string.
279        """
280        from mcmas import parser
281
282        agents = parser.extract_agents(txt)
283        env = agents.pop("Environment", None)
284        assert env is not None
285        return Environment(**env)

Python wrapper for ISPL Environments. Environments model the shared information and boundary conditions that all other agents can observe.

See also the relevant ISPL reference

REQUIRED: ClassVar = ['protocol', 'actions']
def DefaultEnvironment(unknown):
actions: Annotated[List[Annotated[Union[str, mcmas.logic.Symbol], BeforeValidator(func=<class 'str'>, json_schema_input_type=PydanticUndefined)]], BeforeValidator(func=<function ensure_list at 0x7fd1247758a0>, json_schema_input_type=PydanticUndefined)]
vars: Annotated[Optional[Dict[Annotated[Union[str, mcmas.logic.Symbol], BeforeValidator(func=<class 'str'>, json_schema_input_type=PydanticUndefined)], Annotated[Union[str, mcmas.logic.Symbol], BeforeValidator(func=<class 'str'>, json_schema_input_type=PydanticUndefined)]]], BeforeValidator(func=<function ensure_dict at 0x7fd124775b20>, json_schema_input_type=PydanticUndefined)]
obsvars: Annotated[Dict[Annotated[Union[str, mcmas.logic.Symbol], BeforeValidator(func=<class 'str'>, json_schema_input_type=PydanticUndefined)], Annotated[Union[str, mcmas.logic.Symbol], BeforeValidator(func=<class 'str'>, json_schema_input_type=PydanticUndefined)]], BeforeValidator(func=<function ensure_dict at 0x7fd124775b20>, json_schema_input_type=PydanticUndefined)]
evolution: Annotated[List[Annotated[Union[str, mcmas.logic.Symbol], BeforeValidator(func=<class 'str'>, json_schema_input_type=PydanticUndefined)]], BeforeValidator(func=<function ensure_list at 0x7fd1247758a0>, json_schema_input_type=PydanticUndefined)]
protocol: Annotated[Dict[Annotated[Union[str, mcmas.logic.Symbol], BeforeValidator(func=<class 'str'>, json_schema_input_type=PydanticUndefined)], Annotated[Union[str, mcmas.logic.Symbol], BeforeValidator(func=<class 'str'>, json_schema_input_type=PydanticUndefined)]], BeforeValidator(func=<function ensure_protocol at 0x7fd124775940>, json_schema_input_type=PydanticUndefined)]
@classmethod
def get_trivial(kls, *args, **kwargs):
266    @classmethod
267    def get_trivial(kls, *args, **kwargs):
268        kwargs.update(check_slice(*args))
269        actions = kwargs.pop("actions", [symbols.tick])
270        protocol = kwargs.pop("protocol", dict(Other=[symbols.tick]))
271        vars = kwargs.pop("vars", dict(ticking="boolean"))
272        return kls(vars=vars, actions=actions, protocol=protocol, **kwargs)

Subclassers must implement this.

Returns the trivial fragment for this type.

@classmethod
@pydantic.validate_call
def load_from_source(kls, txt: str) -> Self:
274    @classmethod
275    @pydantic.validate_call
276    def load_from_source(kls, txt: str) -> typing.Self:
277        """
278        Creates an Environment from string.
279        """
280        from mcmas import parser
281
282        agents = parser.extract_agents(txt)
283        env = agents.pop("Environment", None)
284        assert env is not None
285        return Environment(**env)

Creates an Environment from string.

model_config: ClassVar[pydantic.config.ConfigDict] = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

Actions = typing.Annotated[typing.List[typing.Annotated[typing.Union[str, mcmas.logic.Symbol], BeforeValidator(func=<class 'str'>, json_schema_input_type=PydanticUndefined)]], BeforeValidator(func=<function ensure_list>, json_schema_input_type=PydanticUndefined)]
DefaultAgent = Agent(metadata=SpecificationMetadata(file=None, engine='mcmas', parser='mcmas.parser'), name='trivial', actions=['tick'], evolution=['ticking=true if Action=tick;'], obsvars={}, protocol={'Other': '{tick}'}, vars={'ticking': 'boolean'}, lobsvars=[], red_states=[])
class Specification(mcmas.fmtk.SpecificationFragment):
154class Specification(SpecificationFragment):
155    """
156    Pydantic models for a Specification.
157    """

Pydantic models for a Specification.

model_config: ClassVar[pydantic.config.ConfigDict] = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class Simulation(mcmas.sim.SimBase):
165class Simulation(SimBase):
166    """
167    A Simulation object is the result of having run a spec.
168
169    In practice a Simulation in `py-mcmas` is always an ISPL program
170    running on an MCMAS engine, but see `SimBase` for something more
171    generic.
172    """

A Simulation object is the result of having run a spec.

In practice a Simulation in py-mcmas is always an ISPL program running on an MCMAS engine, but see SimBase for something more generic.

model_config: ClassVar[pydantic.config.ConfigDict] = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class Society:
222class Society:
223    """
224    Agent-discovery for a few different ecosystems / frameworks.
225    Supported frameworks are { pydantic_ai | openai | camelai }.
226
227    Instantiate a Society with the framework module, or a string
228    version of the module name.  This auto-detects all agents that
229    are defined / instantiated for this runtime.
230
231    These class-properties are also available as shortcuts:
232        * `Society.pydantic`
233        * `Society.openai`
234    """
235
236    def __getitem__(self, other):
237        return self.get_spec(other)
238
239    def get_spec(self, other):
240        """
241        
242        """
243        tmp = [x for x in self]
244        if isinstance(other, (str,)):
245            agent = None
246            for agent in tmp:
247                if getattr(agent, "name", None) == tmp:
248                    break
249        if other in tmp:
250            agent = other
251        return agent_completion(agent=agent, framework=self.module)
252
253    def __init__(self, module):
254        """
255        Instantiate a Society with the framework module, or a
256        string version of the module name.
257
258        This auto-detects all agents that are defined /
259        instantiated for this runtime.
260        """
261        name = getattr(module, "__name__", module)
262        assert name in ["pydantic_ai", "openai", "agents"]
263        self._module = module
264        self._society = []
265        if name == "pydantic_ai":
266            try:
267                import pydantic_ai
268            except ImportError as exc:
269                LOGGER.critical(
270                    "`pydantic_ai` module not available.  "
271                    f"pip install py-mcmas[ai] or openai-agents {exc}"
272                )
273            else:
274                self._society = util.find_instances(pydantic_ai.Agent)
275        elif name in ["openai", "agents"]:
276            try:
277                from agents import Agent
278            except ImportError:
279                LOGGER.critical(
280                    "`agents` module not available.  "
281                    "pip install py-mcmas[ai] or openai-agents"
282                )
283            else:
284                self._society = util.find_instances(Agent)
285
286        else:
287            raise TypeError(f"niy {[type(module), module]}")
288
289    def __iter__(self):
290        return iter(self._society)
291
292    @util.classproperty
293    def pydantic(kls) -> typing.List:
294        """
295        Finds all the pydantic agents.
296        """
297        return kls("pydantic_ai")
298
299    @util.classproperty
300    def openai(kls) -> typing.List:
301        """
302        Finds all the openai agents.
303        """
304        return kls("openai")

Agent-discovery for a few different ecosystems / frameworks. Supported frameworks are { pydantic_ai | openai | camelai }.

Instantiate a Society with the framework module, or a string version of the module name. This auto-detects all agents that are defined / instantiated for this runtime.

These class-properties are also available as shortcuts: * Society.pydantic * Society.openai

Society(module)
253    def __init__(self, module):
254        """
255        Instantiate a Society with the framework module, or a
256        string version of the module name.
257
258        This auto-detects all agents that are defined /
259        instantiated for this runtime.
260        """
261        name = getattr(module, "__name__", module)
262        assert name in ["pydantic_ai", "openai", "agents"]
263        self._module = module
264        self._society = []
265        if name == "pydantic_ai":
266            try:
267                import pydantic_ai
268            except ImportError as exc:
269                LOGGER.critical(
270                    "`pydantic_ai` module not available.  "
271                    f"pip install py-mcmas[ai] or openai-agents {exc}"
272                )
273            else:
274                self._society = util.find_instances(pydantic_ai.Agent)
275        elif name in ["openai", "agents"]:
276            try:
277                from agents import Agent
278            except ImportError:
279                LOGGER.critical(
280                    "`agents` module not available.  "
281                    "pip install py-mcmas[ai] or openai-agents"
282                )
283            else:
284                self._society = util.find_instances(Agent)
285
286        else:
287            raise TypeError(f"niy {[type(module), module]}")

Instantiate a Society with the framework module, or a string version of the module name.

This auto-detects all agents that are defined / instantiated for this runtime.

def get_spec(self, other):
239    def get_spec(self, other):
240        """
241        
242        """
243        tmp = [x for x in self]
244        if isinstance(other, (str,)):
245            agent = None
246            for agent in tmp:
247                if getattr(agent, "name", None) == tmp:
248                    break
249        if other in tmp:
250            agent = other
251        return agent_completion(agent=agent, framework=self.module)
def pydantic(unknown):

Finds all the pydantic agents.

def openai(unknown):

Finds all the openai agents.

@pydantic.validate_call
def engine( fname: Union[str, pathlib.PosixPath] = '', text: str = '', model=None, data: dict = {}, file: Union[str, pathlib.PosixPath] = '', **kwargs) -> Union[Dict, str]:
395@pydantic.validate_call
396def engine(
397    fname: Union[str, PosixPath] = "",
398    text: str = "",
399    model=None,
400    data: dict = {},
401    file: Union[str, PosixPath] = "",
402    **kwargs,
403) -> Union[Dict, str]:
404    """
405    Runs the engine on either a filename, a block of ISPL text,
406    or an ISPL- Specification object.
407    """
408    if text:
409        LOGGER.debug("------------")
410        LOGGER.debug(text)
411        LOGGER.debug("------------")
412        with tempfile.NamedTemporaryFile(
413            mode="w+", suffix=".ispl", delete=True, dir="."
414        ) as temp_file:
415            temp_file.write(text)
416            temp_file.flush()
417            result = mcmas(fname=temp_file.name, **kwargs)
418            # raise Exception(result.metadata)
419            return result
420    # elif file and file=='/dev/stdin':
421    #     import sys
422    #     raise Exception(sys.stdin.read())
423    #     return engine(text=sys.stdin.read())
424    elif model:
425        return engine(data=model.model_dump(), **kwargs)
426    elif fname or file:
427        return mcmas(fname=fname or file, **kwargs)
428    elif data:
429        return engine(text=util.dict2ispl(data), **kwargs)
430    else:
431        err = "No input, expected one of {text|model|data}"
432        raise Exception(err + f"\n{[fname,text,model,data,file]}")

Runs the engine on either a filename, a block of ISPL text, or an ISPL- Specification object.

symbols
class If(mcmas.logic.Function):
61class If(Function):
62    """
63    
64    """
65
66    def __str__(self):
67        """
68        
69        """
70        if len(self._sorted_args) == 1:
71            return f"if {self._sorted_args[0]}"
72        else:
73            assert len(self._sorted_args) == 2, "multiclause if not supported yet"
74            return f"{self._sorted_args[0]} if {self._sorted_args[1]}"
default_assumptions: ClassVar[sympy.core.assumptions.StdFactKB] = {}
class And(mcmas.logic.Function):
24class And(Function):
25    """
26    
27    """
28
29    def __str__(self):
30        """
31        
32        """
33        return " and ".join([str(x) for x in self._sorted_args])
default_assumptions: ClassVar[sympy.core.assumptions.StdFactKB] = {}
Equal = <class 'Eq'>
class Eq(sympy.core.relational.Equality):
77class Eq(_Eq):
78    """
79    
80    """
81
82    def __str__(expr):
83        """
84        
85        """
86        return f"{expr.lhs}={expr.rhs}"
default_assumptions: ClassVar[sympy.core.assumptions.StdFactKB] = {}