Skip to content

Commit bd5a09c

Browse files
authored
Support further Python Interop for callables in .estimate(), .compile(), .run() and .circuit() (#2091)
This follows up on #2054 by adding the enhanced support for Q# callables exposed into Python and Python argument arrays in resource estimation, QIR compilation, running multiple shots, and circuit generation.
1 parent fe18697 commit bd5a09c

File tree

10 files changed

+875
-86
lines changed

10 files changed

+875
-86
lines changed

compiler/qsc/src/interpret.rs

Lines changed: 130 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ use qsc_circuit::{
4242
operations::entry_expr_for_qubit_operation, Builder as CircuitBuilder, Circuit,
4343
Config as CircuitConfig,
4444
};
45-
use qsc_codegen::qir::fir_to_qir;
45+
use qsc_codegen::qir::{fir_to_qir, fir_to_qir_from_callable};
4646
use qsc_data_structures::{
4747
functors::FunctorApp,
4848
language_features::LanguageFeatures,
@@ -103,6 +103,9 @@ pub enum Error {
103103
#[diagnostic(code("Qsc.Interpret.NotAnOperation"))]
104104
#[diagnostic(help("provide the name of a callable or a lambda expression"))]
105105
NotAnOperation,
106+
#[error("value is not a global callable")]
107+
#[diagnostic(code("Qsc.Interpret.NotACallable"))]
108+
NotACallable,
106109
#[error("partial evaluation error")]
107110
#[diagnostic(transparent)]
108111
PartialEvaluation(#[from] WithSource<qsc_partial_eval::Error>),
@@ -563,6 +566,39 @@ impl Interpreter {
563566
})
564567
}
565568

569+
// Invokes the given callable with the given arguments using the current environment and compilation but with a fresh
570+
// simulator configured with the given noise, if any.
571+
pub fn invoke_with_noise(
572+
&mut self,
573+
receiver: &mut impl Receiver,
574+
callable: Value,
575+
args: Value,
576+
noise: Option<PauliNoise>,
577+
) -> InterpretResult {
578+
let mut sim = match noise {
579+
Some(noise) => SparseSim::new_with_noise(&noise),
580+
None => SparseSim::new(),
581+
};
582+
qsc_eval::invoke(
583+
self.package,
584+
self.classical_seed,
585+
&self.fir_store,
586+
&mut self.env,
587+
&mut sim,
588+
receiver,
589+
callable,
590+
args,
591+
)
592+
.map_err(|(error, call_stack)| {
593+
eval_error(
594+
self.compiler.package_store(),
595+
&self.fir_store,
596+
call_stack,
597+
error,
598+
)
599+
})
600+
}
601+
566602
/// Runs the given entry expression on a new instance of the environment and simulator,
567603
/// but using the current compilation.
568604
pub fn run(
@@ -639,6 +675,45 @@ impl Interpreter {
639675
})
640676
}
641677

678+
/// Performs QIR codegen using the given callable with the given arguments on a new instance of the environment
679+
/// and simulator but using the current compilation.
680+
pub fn qirgen_from_callable(
681+
&mut self,
682+
callable: &Value,
683+
args: Value,
684+
) -> std::result::Result<String, Vec<Error>> {
685+
if self.capabilities == TargetCapabilityFlags::all() {
686+
return Err(vec![Error::UnsupportedRuntimeCapabilities]);
687+
}
688+
689+
let Value::Global(store_item_id, _) = callable else {
690+
return Err(vec![Error::NotACallable]);
691+
};
692+
693+
fir_to_qir_from_callable(
694+
&self.fir_store,
695+
self.capabilities,
696+
None,
697+
*store_item_id,
698+
args,
699+
)
700+
.map_err(|e| {
701+
let hir_package_id = match e.span() {
702+
Some(span) => span.package,
703+
None => map_fir_package_to_hir(self.package),
704+
};
705+
let source_package = self
706+
.compiler
707+
.package_store()
708+
.get(hir_package_id)
709+
.expect("package should exist in the package store");
710+
vec![Error::PartialEvaluation(WithSource::from_map(
711+
&source_package.sources,
712+
e,
713+
))]
714+
})
715+
}
716+
642717
/// Generates a circuit representation for the program.
643718
///
644719
/// `entry` can be the current entrypoint, an entry expression, or any operation
@@ -656,21 +731,30 @@ impl Interpreter {
656731
entry: CircuitEntryPoint,
657732
simulate: bool,
658733
) -> std::result::Result<Circuit, Vec<Error>> {
659-
let entry_expr = match entry {
734+
let (entry_expr, invoke_params) = match entry {
660735
CircuitEntryPoint::Operation(operation_expr) => {
661736
let (item, functor_app) = self.eval_to_operation(&operation_expr)?;
662737
let expr = entry_expr_for_qubit_operation(item, functor_app, &operation_expr)
663738
.map_err(|e| vec![e.into()])?;
664-
Some(expr)
739+
(Some(expr), None)
665740
}
666-
CircuitEntryPoint::EntryExpr(expr) => Some(expr),
667-
CircuitEntryPoint::EntryPoint => None,
741+
CircuitEntryPoint::EntryExpr(expr) => (Some(expr), None),
742+
CircuitEntryPoint::Callable(call_val, args_val) => (None, Some((call_val, args_val))),
743+
CircuitEntryPoint::EntryPoint => (None, None),
668744
};
669745

670746
let circuit = if simulate {
671747
let mut sim = sim_circuit_backend();
672748

673-
self.run_with_sim_no_output(entry_expr, &mut sim)?;
749+
match invoke_params {
750+
Some((callable, args)) => {
751+
let mut sink = std::io::sink();
752+
let mut out = GenericReceiver::new(&mut sink);
753+
754+
self.invoke_with_sim(&mut sim, &mut out, callable, args)?
755+
}
756+
None => self.run_with_sim_no_output(entry_expr, &mut sim)?,
757+
};
674758

675759
sim.chained.finish()
676760
} else {
@@ -679,7 +763,15 @@ impl Interpreter {
679763
max_operations: CircuitConfig::DEFAULT_MAX_OPERATIONS,
680764
});
681765

682-
self.run_with_sim_no_output(entry_expr, &mut sim)?;
766+
match invoke_params {
767+
Some((callable, args)) => {
768+
let mut sink = std::io::sink();
769+
let mut out = GenericReceiver::new(&mut sink);
770+
771+
self.invoke_with_sim(&mut sim, &mut out, callable, args)?
772+
}
773+
None => self.run_with_sim_no_output(entry_expr, &mut sim)?,
774+
};
683775

684776
sim.finish()
685777
};
@@ -759,6 +851,35 @@ impl Interpreter {
759851
)
760852
}
761853

854+
/// Invokes the given callable with the given arguments on the given simulator with a new instance of the environment
855+
/// but using the current compilation.
856+
pub fn invoke_with_sim(
857+
&mut self,
858+
sim: &mut impl Backend<ResultType = impl Into<val::Result>>,
859+
receiver: &mut impl Receiver,
860+
callable: Value,
861+
args: Value,
862+
) -> InterpretResult {
863+
qsc_eval::invoke(
864+
self.package,
865+
self.classical_seed,
866+
&self.fir_store,
867+
&mut Env::default(),
868+
sim,
869+
receiver,
870+
callable,
871+
args,
872+
)
873+
.map_err(|(error, call_stack)| {
874+
eval_error(
875+
self.compiler.package_store(),
876+
&self.fir_store,
877+
call_stack,
878+
error,
879+
)
880+
})
881+
}
882+
762883
fn compile_entry_expr(
763884
&mut self,
764885
expr: &str,
@@ -904,6 +1025,8 @@ pub enum CircuitEntryPoint {
9041025
Operation(String),
9051026
/// An explicitly provided entry expression.
9061027
EntryExpr(String),
1028+
/// A global callable with arguments.
1029+
Callable(Value, Value),
9071030
/// The entry point for the current package.
9081031
EntryPoint,
9091032
}

compiler/qsc_codegen/src/qir.rs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ mod instruction_tests;
88
mod tests;
99

1010
use qsc_data_structures::target::TargetCapabilityFlags;
11+
use qsc_eval::val::Value;
1112
use qsc_lowerer::map_hir_package_to_fir;
12-
use qsc_partial_eval::{partially_evaluate, ProgramEntry};
13+
use qsc_partial_eval::{partially_evaluate, partially_evaluate_call, ProgramEntry};
1314
use qsc_rca::PackageStoreComputeProperties;
1415
use qsc_rir::{
1516
passes::check_and_transform,
@@ -37,6 +38,7 @@ pub fn hir_to_qir(
3738
fir_to_qir(&fir_store, capabilities, compute_properties, entry)
3839
}
3940

41+
/// converts the given sources to RIR using the given language features.
4042
pub fn fir_to_rir(
4143
fir_store: &qsc_fir::fir::PackageStore,
4244
capabilities: TargetCapabilityFlags,
@@ -49,6 +51,7 @@ pub fn fir_to_rir(
4951
Ok((orig, program))
5052
}
5153

54+
/// converts the given sources to QIR using the given language features.
5255
pub fn fir_to_qir(
5356
fir_store: &qsc_fir::fir::PackageStore,
5457
capabilities: TargetCapabilityFlags,
@@ -60,6 +63,25 @@ pub fn fir_to_qir(
6063
Ok(ToQir::<String>::to_qir(&program, &program))
6164
}
6265

66+
/// converts the given callable to QIR using the given arguments and language features.
67+
pub fn fir_to_qir_from_callable(
68+
fir_store: &qsc_fir::fir::PackageStore,
69+
capabilities: TargetCapabilityFlags,
70+
compute_properties: Option<PackageStoreComputeProperties>,
71+
callable: qsc_fir::fir::StoreItemId,
72+
args: Value,
73+
) -> Result<String, qsc_partial_eval::Error> {
74+
let compute_properties = compute_properties.unwrap_or_else(|| {
75+
let analyzer = qsc_rca::Analyzer::init(fir_store);
76+
analyzer.analyze_all()
77+
});
78+
79+
let mut program =
80+
partially_evaluate_call(fir_store, &compute_properties, callable, args, capabilities)?;
81+
check_and_transform(&mut program);
82+
Ok(ToQir::<String>::to_qir(&program, &program))
83+
}
84+
6385
fn get_rir_from_compilation(
6486
fir_store: &qsc_fir::fir::PackageStore,
6587
compute_properties: Option<PackageStoreComputeProperties>,

0 commit comments

Comments
 (0)